Kubernetes學習之路(十四)之服務發現Service

1、Service的概念

  運行在Pod中的應用是向客戶端提供服務的守護進程,好比,nginx、tomcat、etcd等等,它們都是受控於控制器的資源對象,存在生命週期,咱們知道Pod資源對象在自願或非自願終端後,只能被重構的Pod對象所替代,屬於不可再生類組件。而在動態和彈性的管理模式下,Service爲該類Pod對象提供了一個固定、統一的訪問接口和負載均衡能力。是否是以爲一堆話都沒聽明白呢????html

  其實,就是說Pod存在生命週期,有銷燬,有重建,沒法提供一個固定的訪問接口給客戶端。而且爲了同類的Pod都可以實現工做負載的價值,由此Service資源出現了,能夠爲一類Pod資源對象提供一個固定的訪問接口和負載均衡,相似於阿里雲的負載均衡或者是LVS的功能。node

  可是要知道的是,Service和Pod對象的IP地址,一個是虛擬地址,一個是Pod IP地址,都僅僅在集羣內部能夠進行訪問,沒法接入集羣外部流量。而爲了解決該類問題的辦法能夠是在單一的節點上作端口暴露(hostPort)以及讓Pod資源共享工做節點的網絡名稱空間(hostNetwork)之外,還可使用NodePort或者是LoadBalancer類型的Service資源,或者是有7層負載均衡能力的Ingress資源。mysql

  Service是Kubernetes的核心資源類型之一,Service資源基於標籤選擇器將一組Pod定義成一個邏輯組合,並經過本身的IP地址和端口調度代理請求到組內的Pod對象,以下圖所示,它向客戶端隱藏了真是的,處理用戶請求的Pod資源,使得從客戶端上看,就像是由Service直接處理並響應同樣,是否是很像負載均衡器呢!nginx

  Service對象的IP地址也稱爲Cluster IP,它位於爲Kubernetes集羣配置指定專用的IP地址範圍以內,是一種虛擬的IP地址,它在Service對象建立以後保持不變,而且可以被同一集羣中的Pod資源所訪問。Service端口用於接受客戶端請求,並將請求轉發至後端的Pod應用的相應端口,這樣的代理機制,也稱爲端口代理,它是基於TCP/IP 協議棧的傳輸層。git

2、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)服務。redis

kube-proxy 這個組件始終監視着apiserver中有關service的變更信息,獲取任何一個與service資源相關的變更狀態,經過watch監視,一旦有service資源相關的變更和建立,kube-proxy都要轉換爲當前節點上的可以實現資源調度規則(例如:iptables、ipvs)算法

2.一、userspace代理模式

  這種模式,當客戶端Pod請求內核空間的service iptables後,把請求轉到給用戶空間監聽的kube-proxy 的端口,由kube-proxy來處理後,再由kube-proxy將請求轉給內核空間的 service ip,再由service iptalbes根據請求轉給各節點中的的service pod。sql

  因而可知這個模式有很大的問題,由客戶端請求先進入內核空間的,又進去用戶空間訪問kube-proxy,由kube-proxy封裝完成後再進去內核空間的iptables,再根據iptables的規則分發給各節點的用戶空間的pod。這樣流量從用戶空間進出內核帶來的性能損耗是不可接受的。在Kubernetes 1.1版本以前,userspace是默認的代理模型。vim

2.二、 iptables代理模式

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

 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也是一樣的原理。如圖:

3、Service的定義

3.一、清單建立Service

 1 [root@k8s-master ~]# kubectl explain svc
 2 KIND:     Service
 3 VERSION:  v1
 4 
 5 DESCRIPTION:
 6      Service is a named abstraction of software service (for example, mysql)
 7      consisting of local port (for example 3306) that the proxy listens on, and
 8      the selector that determines which pods will answer requests sent through
 9      the proxy.
10 
11 FIELDS:
12    apiVersion    <string>
13      APIVersion defines the versioned schema of this representation of an
14      object. Servers should convert recognized schemas to the latest internal
15      value, and may reject unrecognized values. More info:
16      https://git.k8s.io/community/contributors/devel/api-conventions.md#resources
17 
18    kind    <string>
19      Kind is a string value representing the REST resource this object
20      represents. Servers may infer this from the endpoint the client submits
21      requests to. Cannot be updated. In CamelCase. More info:
22      https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
23 
24    metadata    <Object>
25      Standard object's metadata. More info:
26      https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
27 
28    spec    <Object>
29      Spec defines the behavior of a service.
30      https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
31 
32    status    <Object>
33      Most recently observed status of the service. Populated by the system.
34      Read-only. More info:
35      https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
View Code

其中重要的4個字段:
apiVersion:
kind:
metadata:
spec:
  clusterIP: 能夠自定義,也能夠動態分配
  ports:(與後端容器端口關聯)
  selector:(關聯到哪些pod資源上)
  type:服務類型

3.二、service的類型

對一些應用(如 Frontend)的某些部分,可能但願經過外部(Kubernetes 集羣外部)IP 地址暴露 Service。

Kubernetes ServiceTypes 容許指定一個須要的類型的 Service,默認是 ClusterIP 類型。

Type 的取值以及行爲以下:

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

 3.2.一、ClusterIP的service類型演示:

[root@k8s-master mainfests]# cat redis-svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: default
spec:
  selector:  #標籤選擇器,必須指定pod資源自己的標籤
    app: redis
    role: logstor
  type: ClusterIP  #指定服務類型爲ClusterIP
  ports:   #指定端口
  - port: 6379  #暴露給服務的端口
  - targetPort: 6379  #容器的端口
[root@k8s-master mainfests]# kubectl apply -f redis-svc.yaml 
service/redis created
[root@k8s-master mainfests]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    36d
redis        ClusterIP   10.107.238.182   <none>        6379/TCP   1m

[root@k8s-master mainfests]# 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":{"ports":[{"port":6379,"targetPort":6379}...
Selector:          app=redis,role=logstor
Type:              ClusterIP
IP:                10.107.238.182  #service ip
Port:              <unset>  6379/TCP
TargetPort:        6379/TCP
Endpoints:         10.244.1.16:6379  #此處的ip+端口就是pod的ip+端口
Session Affinity:  None
Events:            <none>

[root@k8s-master mainfests]# kubectl get pod redis-5b5d6fbbbd-v82pw -o wide
NAME                     READY     STATUS    RESTARTS   AGE       IP            NODE
redis-5b5d6fbbbd-v82pw   1/1       Running   0          20d       10.244.1.16   k8s-node01

從上演示能夠總結出: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.

3.2.二、NodePort的service類型演示: 

  NodePort即節點Port,一般在部署Kubernetes集羣系統時會預留一個端口範圍用於NodePort,其範圍默認爲:30000~32767之間的端口。定義NodePort類型的Service資源時,須要使用.spec.type進行明確指定。

[root@k8s-master mainfests]# kubectl get pods --show-labels |grep myapp-deploy
myapp-deploy-69b47bc96d-4hxxw   1/1       Running   0          12m       app=myapp,pod-template-hash=2560367528,release=canary
myapp-deploy-69b47bc96d-95bc4   1/1       Running   0          12m       app=myapp,pod-template-hash=2560367528,release=canary
myapp-deploy-69b47bc96d-hwbzt   1/1       Running   0          12m       app=myapp,pod-template-hash=2560367528,release=canary
myapp-deploy-69b47bc96d-pjv74   1/1       Running   0          12m       app=myapp,pod-template-hash=2560367528,release=canary
myapp-deploy-69b47bc96d-rf7bs   1/1       Running   0          12m       app=myapp,pod-template-hash=2560367528,release=canary

[root@k8s-master mainfests]# cat myapp-svc.yaml #爲myapp建立service
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  type: NodePort
  ports: 
  - port: 80
    targetPort: 80
    nodePort: 30080
[root@k8s-master mainfests]# kubectl apply -f myapp-svc.yaml 
service/myapp created
[root@k8s-master mainfests]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        36d
myapp NodePort 10.101.245.119 <none> 80:30080/TCP 5s
redis        ClusterIP   10.107.238.182   <none>        6379/TCP       28m

[root@k8s-master mainfests]# while true;do curl http://192.168.56.11:30080/hostname.html;sleep 1;done
myapp-deploy-69b47bc96d-95bc4
myapp-deploy-69b47bc96d-4hxxw
myapp-deploy-69b47bc96d-pjv74
myapp-deploy-69b47bc96d-rf7bs
myapp-deploy-69b47bc96d-95bc4
myapp-deploy-69b47bc96d-rf7bs
myapp-deploy-69b47bc96d-95bc4
myapp-deploy-69b47bc96d-pjv74
myapp-deploy-69b47bc96d-4hxxw
myapp-deploy-69b47bc96d-pjv74
myapp-deploy-69b47bc96d-pjv74
myapp-deploy-69b47bc96d-4hxxw
myapp-deploy-69b47bc96d-pjv74
myapp-deploy-69b47bc96d-pjv74
myapp-deploy-69b47bc96d-pjv74
myapp-deploy-69b47bc96d-95bc4
myapp-deploy-69b47bc96d-hwbzt

[root@k8s-master mainfests]# while true;do curl http://192.168.56.11:30080/;sleep 1;done

  Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
  Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
  Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
  Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
  Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
  Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

從以上例子,能夠看到經過NodePort方式已經實現了從集羣外部端口進行訪問,訪問連接以下:http://192.168.56.11:30080/。實踐中並不鼓勵用戶自定義使用節點的端口,由於容易和其餘現存的Service衝突,建議留給系統自動配置。

 3.2.三、Pod的會話保持

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

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

[root@k8s-master mainfests]# 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@k8s-master mainfests]# vim myapp-svc.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: 30080
[root@k8s-master mainfests]# kubectl apply -f myapp-svc.yaml 
service/myapp configured
[root@k8s-master mainfests]# 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":30080,"port":80,"ta...
Selector:                 app=myapp,release=canary
Type:                     NodePort
IP:                       10.101.245.119
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30080/TCP
Endpoints:                10.244.1.18:80,10.244.1.19:80,10.244.2.15:80 + 2 more...
Session Affinity:         ClientIP
External Traffic Policy:  Cluster
Events:                   <none>
[root@k8s-master mainfests]# while true;do curl http://192.168.56.11:30080/hostname.html;sleep 1;done
myapp-deploy-69b47bc96d-hwbzt
myapp-deploy-69b47bc96d-hwbzt
myapp-deploy-69b47bc96d-hwbzt
myapp-deploy-69b47bc96d-hwbzt
myapp-deploy-69b47bc96d-hwbzt
myapp-deploy-69b47bc96d-hwbzt
myapp-deploy-69b47bc96d-hwbzt
myapp-deploy-69b47bc96d-hwbzt

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

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

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

4、Headless Service 

有時不須要或不想要負載均衡,以及單獨的 Service IP。 遇到這種狀況,能夠經過指定 Cluster IP(spec.clusterIP)的值爲 "None" 來建立 Headless Service。

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

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

(1)編寫headless service配置清單
[root@k8s-master mainfests]# cp myapp-svc.yaml myapp-svc-headless.yaml [root@k8s-master mainfests]# vim myapp-svc-headless.yaml apiVersion: v1 kind: Service metadata: name: myapp-headless namespace: default spec: selector: app: myapp release: canary clusterIP: "None"  #headless的clusterIP值爲None ports: - port: 80 targetPort: 80
(2)建立headless service [root@k8s
-master mainfests]# kubectl apply -f myapp-svc-headless.yaml service/myapp-headless created [root@k8s-master mainfests]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 36d myapp NodePort 10.101.245.119 <none> 80:30080/TCP 1h myapp-headless ClusterIP None <none> 80/TCP 5s redis ClusterIP 10.107.238.182 <none> 6379/TCP 2h
(3)使用coredns進行解析驗證 [root@k8s
-master mainfests]# dig -t A myapp-headless.default.svc.cluster.local. @10.96.0.10 ; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp-headless.default.svc.cluster.local. @10.96.0.10 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62028 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;myapp-headless.default.svc.cluster.local. IN A ;; ANSWER SECTION: myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.18 myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.19 myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.15 myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.16 myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.17 ;; Query time: 4 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) ;; WHEN: Thu Sep 27 04:27:15 EDT 2018 ;; MSG SIZE rcvd: 349 [root@k8s-master mainfests]# 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 36d [root@k8s-master mainfests]# kubectl get pods -o wide -l app=myapp NAME READY STATUS RESTARTS AGE IP NODE myapp-deploy-69b47bc96d-4hxxw 1/1 Running 0 1h 10.244.1.18 k8s-node01 myapp-deploy-69b47bc96d-95bc4 1/1 Running 0 1h 10.244.2.16 k8s-node02 myapp-deploy-69b47bc96d-hwbzt 1/1 Running 0 1h 10.244.1.19 k8s-node01 myapp-deploy-69b47bc96d-pjv74 1/1 Running 0 1h 10.244.2.15 k8s-node02 myapp-deploy-69b47bc96d-rf7bs 1/1 Running 0 1h 10.244.2.17 k8s-node02
(4)對比含有ClusterIP的service解析 [root@k8s
-master mainfests]# dig -t A myapp.default.svc.cluster.local. @10.96.0.10 ; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp.default.svc.cluster.local. @10.96.0.10 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50445 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;myapp.default.svc.cluster.local. IN A ;; ANSWER SECTION: myapp.default.svc.cluster.local. 5 IN A 10.101.245.119 ;; Query time: 1 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) ;; WHEN: Thu Sep 27 04:31:16 EDT 2018 ;; MSG SIZE rcvd: 107 [root@k8s-master mainfests]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 36d myapp NodePort 10.101.245.119 <none> 80:30080/TCP 1h myapp-headless ClusterIP None <none> 80/TCP 11m redis ClusterIP 10.107.238.182 <none> 6379/TCP 2h

從以上的演示能夠看到對比普通的service和headless service,headless service作dns解析是直接解析到pod的,而servcie是解析到ClusterIP的,那麼headless有什麼用呢???這將在statefulset中應用到,這裏暫時僅僅作了解什麼是headless service和建立方法。

相關文章
相關標籤/搜索