一、Service的概念
運行在Pod中的應用是向客戶端提供服務的守護進程,比如,nginx、tomcat、etcd等等,它們都是受控於控制器的資源對象,存在生命周期,我們知道Pod資源對象在自願或非自願終端后,只能被重構的Pod對象所替代,屬於不可再生類組件。而在動態和彈性的管理模式下,Service為該類Pod對象提供了一個固定、統一的訪問接口和負載均衡能力。是不是覺得一堆話都沒聽明白呢????
其實,就是說Pod存在生命周期,有銷毀,有重建,無法提供一個固定的訪問接口給客戶端。並且為了同類的Pod都能夠實現工作負載的價值,由此Service資源出現了,可以為一類Pod資源對象提供一個固定的訪問接口和負載均衡,類似於阿里雲的負載均衡或者是LVS的功能。
但是要知道的是,Service和Pod對象的IP地址,一個是虛擬地址,一個是Pod IP地址,都僅僅在集群內部可以進行訪問,無法接入集群外部流量。而為了解決該類問題的辦法可以是在單一的節點上做端口暴露(hostPort)以及讓Pod資源共享工作節點的網絡名稱空間(hostNetwork)以外,還可以使用NodePort或者是LoadBalancer類型的Service資源,或者是有7層負載均衡能力的Ingress資源。
Service是Kubernetes的核心資源類型之一,Service資源基於標簽選擇器將一組Pod定義成一個邏輯組合,並通過自己的IP地址和端口調度代理請求到組內的Pod對象,如下圖所示,它向客戶端隱藏了真是的,處理用戶請求的Pod資源,使得從客戶端上看,就像是由Service直接處理並響應一樣,是不是很像負載均衡器呢!
Service對象的IP地址也稱為Cluster IP,它位於為Kubernetes集群配置指定專用的IP地址范圍之內,是一種虛擬的IP地址,它在Service對象創建之后保持不變,並且能夠被同一集群中的Pod資源所訪問。Service端口用於接受客戶端請求,並將請求轉發至后端的Pod應用的相應端口,這樣的代理機制,也稱為端口代理,它是基於TCP/IP 協議棧的傳輸層。
二、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)
2.1、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是默認的代理模型。
2.2、 iptables代理模式
客戶端IP請求時,直接請求本地內核service ip,根據iptables的規則直接將請求轉發到到各pod上,因為使用iptable NAT來完成轉發,也存在不可忽視的性能損耗。另外,如果集群中存在上萬的Service/Endpoint,那么Node上的iptables rules將會非常龐大,性能還會再打折扣。iptables代理模式由Kubernetes 1.1版本引入,自1.2版本開始成為默認類型。
2.3、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的定義
3.1、清單創建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
其中重要的4個字段:
apiVersion:
kind:
metadata:
spec:
clusterIP: 可以自定義,也可以動態分配
ports:(與后端容器端口關聯)
selector:(關聯到哪些pod資源上)
type:服務類型
3.2、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.1、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.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.3、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
四、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和創建方法。