為什么需要 service
在 kubernetes 中,當(dāng)創(chuàng)建帶有多個(gè)副本的 deployment 時(shí),kubernetes 會(huì)創(chuàng)建出多個(gè) pod,此時(shí)即一個(gè)服務(wù)后端有多個(gè)容器,那么在 kubernetes 中負(fù)載均衡怎么做,容器漂移后 ip 也會(huì)發(fā)生變化,如何做服務(wù)發(fā)現(xiàn)以及會(huì)話保持?這就是 service 的作用,service 是一組具有相同 label pod 集合的抽象,集群內(nèi)外的各個(gè)服務(wù)可以通過 service 進(jìn)行互相通信,當(dāng)創(chuàng)建一個(gè) service 對(duì)象時(shí)也會(huì)對(duì)應(yīng)創(chuàng)建一個(gè) endpoint 對(duì)象,endpoint 是用來做容器發(fā)現(xiàn)的,service 只是將多個(gè) pod 進(jìn)行關(guān)聯(lián),實(shí)際的路由轉(zhuǎn)發(fā)都是由 kubernetes 中的 kube-proxy 組件來實(shí)現(xiàn),因此,service 必須結(jié)合 kube-proxy 使用,kube-proxy 組件可以運(yùn)行在 kubernetes 集群中的每一個(gè)節(jié)點(diǎn)上也可以只運(yùn)行在單獨(dú)的幾個(gè)節(jié)點(diǎn)上,其會(huì)根據(jù) service 和 endpoints 的變動(dòng)來改變節(jié)點(diǎn)上 iptables 或者 ipvs 中保存的路由規(guī)則。
service 的工作原理

endpoints controller 是負(fù)責(zé)生成和維護(hù)所有 endpoints 對(duì)象的控制器,監(jiān)聽 service 和對(duì)應(yīng) pod 的變化,更新對(duì)應(yīng) service 的 endpoints 對(duì)象。當(dāng)用戶創(chuàng)建 service 后 endpoints controller 會(huì)監(jiān)聽 pod 的狀態(tài),當(dāng) pod 處于 running 且準(zhǔn)備就緒時(shí),endpoints controller 會(huì)將 pod ip 記錄到 endpoints 對(duì)象中,因此,service 的容器發(fā)現(xiàn)是通過 endpoints 來實(shí)現(xiàn)的。而 kube-proxy 會(huì)監(jiān)聽 service 和 endpoints 的更新并調(diào)用其代理模塊在主機(jī)上刷新路由轉(zhuǎn)發(fā)規(guī)則。
service 的負(fù)載均衡
上文已經(jīng)提到 service 實(shí)際的路由轉(zhuǎn)發(fā)都是由 kube-proxy 組件來實(shí)現(xiàn)的,service 僅以一種 VIP(ClusterIP) 的形式存在,kube-proxy 主要實(shí)現(xiàn)了集群內(nèi)部從 pod 到 service 和集群外部從 nodePort 到 service 的訪問,kube-proxy 的路由轉(zhuǎn)發(fā)規(guī)則是通過其后端的代理模塊實(shí)現(xiàn)的,kube-proxy 的代理模塊目前有四種實(shí)現(xiàn)方案,userspace、iptables、ipvs、kernelspace,其發(fā)展歷程如下所示:
- kubernetes v1.0:services 僅是一個(gè)“4層”代理,代理模塊只有 userspace
- kubernetes v1.1:Ingress API 出現(xiàn),其代理“7層”服務(wù),并且增加了 iptables 代理模塊
- kubernetes v1.2:iptables 成為默認(rèn)代理模式
- kubernetes v1.8:引入 ipvs 代理模塊
- kubernetes v1.9:ipvs 代理模塊成為 beta 版本
- kubernetes v1.11:ipvs 代理模式 GA
在每種模式下都有自己的負(fù)載均衡策略,下文會(huì)詳解介紹。
userspace 模式
在 userspace 模式下,訪問服務(wù)的請(qǐng)求到達(dá)節(jié)點(diǎn)后首先進(jìn)入內(nèi)核 iptables,然后回到用戶空間,由 kube-proxy 轉(zhuǎn)發(fā)到后端的 pod,這樣流量從用戶空間進(jìn)出內(nèi)核帶來的性能損耗是不可接受的,所以也就有了 iptables 模式。
為什么 userspace 模式要建立 iptables 規(guī)則,因?yàn)?kube-proxy 監(jiān)聽的端口在用戶空間,這個(gè)端口不是服務(wù)的訪問端口也不是服務(wù)的 nodePort,因此需要一層 iptables 把訪問服務(wù)的連接重定向給 kube-proxy 服務(wù)。

iptables 模式
iptables 模式是目前默認(rèn)的代理方式,基于 netfilter 實(shí)現(xiàn)。當(dāng)客戶端請(qǐng)求 service 的 ClusterIP 時(shí),根據(jù) iptables 規(guī)則路由到各 pod 上,iptables 使用 DNAT 來完成轉(zhuǎn)發(fā),其采用了隨機(jī)數(shù)實(shí)現(xiàn)負(fù)載均衡。
iptables 模式與 userspace 模式最大的區(qū)別在于,iptables 模塊使用 DNAT 模塊實(shí)現(xiàn)了 service 入口地址到 pod 實(shí)際地址的轉(zhuǎn)換,免去了一次內(nèi)核態(tài)到用戶態(tài)的切換,另一個(gè)與 userspace 代理模式不同的是,如果 iptables 代理最初選擇的那個(gè) pod 沒有響應(yīng),它不會(huì)自動(dòng)重試其他 pod。
iptables 模式最主要的問題是在 service 數(shù)量大的時(shí)候會(huì)產(chǎn)生太多的 iptables 規(guī)則,使用非增量式更新會(huì)引入一定的時(shí)延,大規(guī)模情況下有明顯的性能問題。

ipvs 模式
當(dāng)集群規(guī)模比較大時(shí),iptables 規(guī)則刷新會(huì)非常慢,難以支持大規(guī)模集群,因其底層路由表的實(shí)現(xiàn)是鏈表,對(duì)路由規(guī)則的增刪改查都要涉及遍歷一次鏈表,ipvs 的問世正是解決此問題的,ipvs 是 LVS 的負(fù)載均衡模塊,與 iptables 比較像的是,ipvs 的實(shí)現(xiàn)雖然也基于 netfilter 的鉤子函數(shù),但是它卻使用哈希表作為底層的數(shù)據(jù)結(jié)構(gòu)并且工作在內(nèi)核態(tài),也就是說 ipvs 在重定向流量和同步代理規(guī)則有著更好的性能,幾乎允許無限的規(guī)模擴(kuò)張。
ipvs 支持三種負(fù)載均衡模式:DR模式(Direct Routing)、NAT 模式(Network Address Translation)、Tunneling(也稱 ipip 模式)。三種模式中只有 NAT 支持端口映射,所以 ipvs 使用 NAT 模式。linux 內(nèi)核原生的 ipvs 只支持 DNAT,當(dāng)在數(shù)據(jù)包過濾,SNAT 和支持 NodePort 類型的服務(wù)這幾個(gè)場(chǎng)景中ipvs 還是會(huì)使用 iptables。
此外,ipvs 也支持更多的負(fù)載均衡算法,例如:
- rr:round-robin/輪詢
- lc:least connection/最少連接
- dh:destination hashing/目標(biāo)哈希
- sh:source hashing/源哈希
- sed:shortest expected delay/預(yù)計(jì)延遲時(shí)間最短
- nq:never queue/從不排隊(duì)
userspace、iptables、ipvs 三種模式中默認(rèn)的負(fù)載均衡策略都是通過 round-robin 算法來選擇后端 pod 的,在 service 中可以通過設(shè)置 service.spec.sessionAffinity 的值實(shí)現(xiàn)基于客戶端 ip 的會(huì)話親和性,service.spec.sessionAffinity 的值默認(rèn)為"None",可以設(shè)置為 "ClientIP",此外也可以使用 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 設(shè)置會(huì)話保持時(shí)間。kernelspace 主要是在 windows 下使用的,本文暫且不談。
service 的類型
service 支持的類型也就是 kubernetes 中服務(wù)暴露的方式,默認(rèn)有四種 ClusterIP、NodePort、LoadBalancer、ExternelName,此外還有 Ingress,下面會(huì)詳細(xì)介紹每種類型 service 的具體使用場(chǎng)景。
ClusterIP
ClusterIP 類型的 service 是 kubernetes 集群默認(rèn)的服務(wù)暴露方式,它只能用于集群內(nèi)部通信,可以被各 pod 訪問,其訪問方式為:
pod ---> ClusterIP:ServicePort --> (iptables)DNAT --> PodIP:containePort
ClusterIP Service 類型的結(jié)構(gòu)如下圖所示:

NodePort
如果你想要在集群外訪問集群內(nèi)部的服務(wù),可以使用這種類型的 service,NodePort 類型的 service 會(huì)在集群內(nèi)部署了 kube-proxy 的節(jié)點(diǎn)打開一個(gè)指定的端口,之后所有的流量直接發(fā)送到這個(gè)端口,然后會(huì)被轉(zhuǎn)發(fā)到 service 后端真實(shí)的服務(wù)進(jìn)行訪問。Nodeport 構(gòu)建在 ClusterIP 上,其訪問鏈路如下所示:
client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort
其對(duì)應(yīng)具體的 iptables 規(guī)則會(huì)在后文進(jìn)行講解。
NodePort service 類型的結(jié)構(gòu)如下圖所示:

LoadBalancer
LoadBalancer 類型的 service 通常和云廠商的 LB 結(jié)合一起使用,用于將集群內(nèi)部的服務(wù)暴露到外網(wǎng),云廠商的 LoadBalancer 會(huì)給用戶分配一個(gè) IP,之后通過該 IP 的流量會(huì)轉(zhuǎn)發(fā)到你的 service 上。
LoadBalancer service 類型的結(jié)構(gòu)如下圖所示:

ExternelName
通過 CNAME 將 service 與 externalName 的值(比如:foo.bar.example.com)映射起來,這種方式用的比較少。
Ingress
Ingress 其實(shí)不是 service 的一個(gè)類型,但是它可以作用于多個(gè) service,被稱為 service 的 service,作為集群內(nèi)部服務(wù)的入口,Ingress 作用在七層,可以根據(jù)不同的 url,將請(qǐng)求轉(zhuǎn)發(fā)到不同的 service 上。
Ingress 的結(jié)構(gòu)如下圖所示:

service 的服務(wù)發(fā)現(xiàn)
雖然 service 的 endpoints 解決了容器發(fā)現(xiàn)問題,但不提前知道 service 的 Cluster IP,怎么發(fā)現(xiàn) service 服務(wù)呢?service 當(dāng)前支持兩種類型的服務(wù)發(fā)現(xiàn)機(jī)制,一種是通過環(huán)境變量,另一種是通過 DNS。在這兩種方案中,建議使用后者。
環(huán)境變量
當(dāng)一個(gè) pod 創(chuàng)建完成之后,kubelet 會(huì)在該 pod 中注冊(cè)該集群已經(jīng)創(chuàng)建的所有 service 相關(guān)的環(huán)境變量,但是需要注意的是,在 service 創(chuàng)建之前的所有 pod 是不會(huì)注冊(cè)該環(huán)境變量的,所以在平時(shí)使用時(shí),建議通過 DNS 的方式進(jìn)行 service 之間的服務(wù)發(fā)現(xiàn)。
DNS
可以在集群中部署 CoreDNS 服務(wù)(舊版本的 kubernetes 群使用的是 kubeDNS), 來達(dá)到集群內(nèi)部的 pod 通過DNS 的方式進(jìn)行集群內(nèi)部各個(gè)服務(wù)之間的通訊。
當(dāng)前 kubernetes 集群默認(rèn)使用 CoreDNS 作為默認(rèn)的 DNS 服務(wù),主要原因是 CoreDNS 是基于 Plugin 的方式進(jìn)行擴(kuò)展的,簡(jiǎn)單,靈活,并且不完全被Kubernetes所捆綁。
Service 的使用
ClusterIP 方式
apiVersion: v1
kind: Service
metadata:
name: my-nginx
spec:
clusterIP: 10.105.146.177
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: my-nginx
sessionAffinity: None
type: ClusterIP
NodePort 方式
apiVersion: v1
kind: Service
metadata:
name: my-nginx
spec:
ports:
- nodePort: 30090
port: 80
protocol: TCP
targetPort: 8080
selector:
app: my-nginx
sessionAffinity: None
type: NodePort
其中 nodeport 字段表示通過 nodeport 方式訪問的端口,port 表示通過 service 方式訪問的端口,targetPort 表示 container port。
Headless service(就是沒有 Cluster IP 的 service )
當(dāng)不需要負(fù)載均衡以及單獨(dú)的 ClusterIP 時(shí),可以通過指定 spec.clusterIP 的值為 None 來創(chuàng)建 Headless service,它會(huì)給一個(gè)集群內(nèi)部的每個(gè)成員提供一個(gè)唯一的 DNS 域名來作為每個(gè)成員的網(wǎng)絡(luò)標(biāo)識(shí),集群內(nèi)部成員之間使用域名通信。
apiVersion: v1
kind: Service
metadata:
name: my-nginx
spec:
clusterIP: None
ports:
- nodePort: 30090
port: 80
protocol: TCP
targetPort: 8080
selector:
app: my-nginx
總結(jié)
本文主要講了 kubernetes 中 service 的原理、實(shí)現(xiàn)以及使用方式,service 目前主要有 5 種服務(wù)暴露方式,service 的容器發(fā)現(xiàn)是通過 endpoints 來實(shí)現(xiàn)的,其服務(wù)發(fā)現(xiàn)主要是通過 DNS 實(shí)現(xiàn)的,其負(fù)載均衡以及流量轉(zhuǎn)發(fā)是通過 kube-proxy 實(shí)現(xiàn)的。在后面的文章我會(huì)繼續(xù)介紹 kube-proxy 的設(shè)計(jì)及實(shí)現(xiàn)。
參考: