為什么需要 service
在 kubernetes 中辟犀,當創(chuàng)建帶有多個副本的 deployment 時,kubernetes 會創(chuàng)建出多個 pod,此時即一個服務(wù)后端有多個容器钢属,那么在 kubernetes 中負載均衡怎么做,容器漂移后 ip 也會發(fā)生變化门躯,如何做服務(wù)發(fā)現(xiàn)以及會話保持署咽?這就是 service 的作用,service 是一組具有相同 label pod 集合的抽象生音,集群內(nèi)外的各個服務(wù)可以通過 service 進行互相通信宁否,當創(chuàng)建一個 service 對象時也會對應創(chuàng)建一個 endpoint 對象,endpoint 是用來做容器發(fā)現(xiàn)的缀遍,service 只是將多個 pod 進行關(guān)聯(lián)慕匠,實際的路由轉(zhuǎn)發(fā)都是由 kubernetes 中的 kube-proxy 組件來實現(xiàn),因此域醇,service 必須結(jié)合 kube-proxy 使用台谊,kube-proxy 組件可以運行在 kubernetes 集群中的每一個節(jié)點上也可以只運行在單獨的幾個節(jié)點上,其會根據(jù) service 和 endpoints 的變動來改變節(jié)點上 iptables 或者 ipvs 中保存的路由規(guī)則譬挚。
service 的工作原理
endpoints controller 是負責生成和維護所有 endpoints 對象的控制器锅铅,監(jiān)聽 service 和對應 pod 的變化,更新對應 service 的 endpoints 對象减宣。當用戶創(chuàng)建 service 后 endpoints controller 會監(jiān)聽 pod 的狀態(tài)盐须,當 pod 處于 running 且準備就緒時,endpoints controller 會將 pod ip 記錄到 endpoints 對象中漆腌,因此贼邓,service 的容器發(fā)現(xiàn)是通過 endpoints 來實現(xiàn)的阶冈。而 kube-proxy 會監(jiān)聽 service 和 endpoints 的更新并調(diào)用其代理模塊在主機上刷新路由轉(zhuǎn)發(fā)規(guī)則。
service 的負載均衡
上文已經(jīng)提到 service 實際的路由轉(zhuǎn)發(fā)都是由 kube-proxy 組件來實現(xiàn)的塑径,service 僅以一種 VIP(ClusterIP) 的形式存在女坑,kube-proxy 主要實現(xiàn)了集群內(nèi)部從 pod 到 service 和集群外部從 nodePort 到 service 的訪問,kube-proxy 的路由轉(zhuǎn)發(fā)規(guī)則是通過其后端的代理模塊實現(xiàn)的统舀,kube-proxy 的代理模塊目前有四種實現(xiàn)方案匆骗,userspace、iptables誉简、ipvs绰筛、kernelspace,其發(fā)展歷程如下所示:
- kubernetes v1.0:services 僅是一個“4層”代理描融,代理模塊只有 userspace
- kubernetes v1.1:Ingress API 出現(xiàn),其代理“7層”服務(wù)衡蚂,并且增加了 iptables 代理模塊
- kubernetes v1.2:iptables 成為默認代理模式
- kubernetes v1.8:引入 ipvs 代理模塊
- kubernetes v1.9:ipvs 代理模塊成為 beta 版本
- kubernetes v1.11:ipvs 代理模式 GA
在每種模式下都有自己的負載均衡策略窿克,下文會詳解介紹。
userspace 模式
在 userspace 模式下毛甲,訪問服務(wù)的請求到達節(jié)點后首先進入內(nèi)核 iptables年叮,然后回到用戶空間,由 kube-proxy 轉(zhuǎn)發(fā)到后端的 pod玻募,這樣流量從用戶空間進出內(nèi)核帶來的性能損耗是不可接受的只损,所以也就有了 iptables 模式。
為什么 userspace 模式要建立 iptables 規(guī)則七咧,因為 kube-proxy 監(jiān)聽的端口在用戶空間跃惫,這個端口不是服務(wù)的訪問端口也不是服務(wù)的 nodePort,因此需要一層 iptables 把訪問服務(wù)的連接重定向給 kube-proxy 服務(wù)艾栋。
iptables 模式
iptables 模式是目前默認的代理方式爆存,基于 netfilter 實現(xiàn)。當客戶端請求 service 的 ClusterIP 時蝗砾,根據(jù) iptables 規(guī)則路由到各 pod 上先较,iptables 使用 DNAT 來完成轉(zhuǎn)發(fā),其采用了隨機數(shù)實現(xiàn)負載均衡悼粮。
iptables 模式與 userspace 模式最大的區(qū)別在于闲勺,iptables 模塊使用 DNAT 模塊實現(xiàn)了 service 入口地址到 pod 實際地址的轉(zhuǎn)換,免去了一次內(nèi)核態(tài)到用戶態(tài)的切換扣猫,另一個與 userspace 代理模式不同的是菜循,如果 iptables 代理最初選擇的那個 pod 沒有響應,它不會自動重試其他 pod申尤。
iptables 模式最主要的問題是在 service 數(shù)量大的時候會產(chǎn)生太多的 iptables 規(guī)則债朵,使用非增量式更新會引入一定的時延子眶,大規(guī)模情況下有明顯的性能問題。
ipvs 模式
當集群規(guī)模比較大時序芦,iptables 規(guī)則刷新會非常慢臭杰,難以支持大規(guī)模集群,因其底層路由表的實現(xiàn)是鏈表谚中,對路由規(guī)則的增刪改查都要涉及遍歷一次鏈表渴杆,ipvs 的問世正是解決此問題的,ipvs 是 LVS 的負載均衡模塊宪塔,與 iptables 比較像的是磁奖,ipvs 的實現(xiàn)雖然也基于 netfilter 的鉤子函數(shù),但是它卻使用哈希表作為底層的數(shù)據(jù)結(jié)構(gòu)并且工作在內(nèi)核態(tài)某筐,也就是說 ipvs 在重定向流量和同步代理規(guī)則有著更好的性能比搭,幾乎允許無限的規(guī)模擴張。
ipvs 支持三種負載均衡模式:DR模式(Direct Routing)南誊、NAT 模式(Network Address Translation)身诺、Tunneling(也稱 ipip 模式)。三種模式中只有 NAT 支持端口映射抄囚,所以 ipvs 使用 NAT 模式霉赡。linux 內(nèi)核原生的 ipvs 只支持 DNAT,當在數(shù)據(jù)包過濾幔托,SNAT 和支持 NodePort 類型的服務(wù)這幾個場景中ipvs 還是會使用 iptables穴亏。
此外,ipvs 也支持更多的負載均衡算法重挑,例如:
- rr:round-robin/輪詢
- lc:least connection/最少連接
- dh:destination hashing/目標哈希
- sh:source hashing/源哈希
- sed:shortest expected delay/預計延遲時間最短
- nq:never queue/從不排隊
userspace嗓化、iptables、ipvs 三種模式中默認的負載均衡策略都是通過 round-robin 算法來選擇后端 pod 的谬哀,在 service 中可以通過設(shè)置 service.spec.sessionAffinity
的值實現(xiàn)基于客戶端 ip 的會話親和性蟆湖,service.spec.sessionAffinity
的值默認為"None",可以設(shè)置為 "ClientIP"玻粪,此外也可以使用 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
設(shè)置會話保持時間隅津。kernelspace 主要是在 windows 下使用的,本文暫且不談劲室。
service 的類型
service 支持的類型也就是 kubernetes 中服務(wù)暴露的方式伦仍,默認有四種 ClusterIP、NodePort很洋、LoadBalancer充蓝、ExternelName,此外還有 Ingress,下面會詳細介紹每種類型 service 的具體使用場景谓苟。
ClusterIP
ClusterIP 類型的 service 是 kubernetes 集群默認的服務(wù)暴露方式官脓,它只能用于集群內(nèi)部通信,可以被各 pod 訪問涝焙,其訪問方式為:
pod ---> ClusterIP:ServicePort --> (iptables)DNAT --> PodIP:containePort
ClusterIP Service 類型的結(jié)構(gòu)如下圖所示:
NodePort
如果你想要在集群外訪問集群內(nèi)部的服務(wù)卑笨,可以使用這種類型的 service,NodePort 類型的 service 會在集群內(nèi)部署了 kube-proxy 的節(jié)點打開一個指定的端口仑撞,之后所有的流量直接發(fā)送到這個端口赤兴,然后會被轉(zhuǎn)發(fā)到 service 后端真實的服務(wù)進行訪問。Nodeport 構(gòu)建在 ClusterIP 上隧哮,其訪問鏈路如下所示:
client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort
其對應具體的 iptables 規(guī)則會在后文進行講解桶良。
NodePort service 類型的結(jié)構(gòu)如下圖所示:
LoadBalancer
LoadBalancer 類型的 service 通常和云廠商的 LB 結(jié)合一起使用,用于將集群內(nèi)部的服務(wù)暴露到外網(wǎng)沮翔,云廠商的 LoadBalancer 會給用戶分配一個 IP陨帆,之后通過該 IP 的流量會轉(zhuǎn)發(fā)到你的 service 上。
LoadBalancer service 類型的結(jié)構(gòu)如下圖所示:
ExternelName
通過 CNAME 將 service 與 externalName 的值(比如:foo.bar.example.com)映射起來采蚀,這種方式用的比較少疲牵。
Ingress
Ingress 其實不是 service 的一個類型,但是它可以作用于多個 service搏存,被稱為 service 的 service,作為集群內(nèi)部服務(wù)的入口矢洲,Ingress 作用在七層璧眠,可以根據(jù)不同的 url,將請求轉(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 當前支持兩種類型的服務(wù)發(fā)現(xiàn)機制灾螃,一種是通過環(huán)境變量,另一種是通過 DNS揩徊。在這兩種方案中腰鬼,建議使用后者。
環(huán)境變量
當一個 pod 創(chuàng)建完成之后塑荒,kubelet 會在該 pod 中注冊該集群已經(jīng)創(chuàng)建的所有 service 相關(guān)的環(huán)境變量熄赡,但是需要注意的是,在 service 創(chuàng)建之前的所有 pod 是不會注冊該環(huán)境變量的齿税,所以在平時使用時彼硫,建議通過 DNS 的方式進行 service 之間的服務(wù)發(fā)現(xiàn)。
DNS
可以在集群中部署 CoreDNS 服務(wù)(舊版本的 kubernetes 群使用的是 kubeDNS), 來達到集群內(nèi)部的 pod 通過DNS 的方式進行集群內(nèi)部各個服務(wù)之間的通訊拧篮。
當前 kubernetes 集群默認使用 CoreDNS 作為默認的 DNS 服務(wù)词渤,主要原因是 CoreDNS 是基于 Plugin 的方式進行擴展的,簡單串绩,靈活缺虐,并且不完全被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 )
當不需要負載均衡以及單獨的 ClusterIP 時把篓,可以通過指定 spec.clusterIP
的值為 None
來創(chuàng)建 Headless service纫溃,它會給一個集群內(nèi)部的每個成員提供一個唯一的 DNS 域名來作為每個成員的網(wǎng)絡(luò)標識,集群內(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 的原理紊浩、實現(xiàn)以及使用方式,service 目前主要有 5 種服務(wù)暴露方式疗锐,service 的容器發(fā)現(xiàn)是通過 endpoints 來實現(xiàn)的坊谁,其服務(wù)發(fā)現(xiàn)主要是通過 DNS 實現(xiàn)的,其負載均衡以及流量轉(zhuǎn)發(fā)是通過 kube-proxy 實現(xiàn)的滑臊。在后面的文章我會繼續(xù)介紹 kube-proxy 的設(shè)計及實現(xiàn)口芍。
參考: