叢集服務實作
近些年來,k8s使用也是越來越多,在很多領域都已經開始使用k8s了。今天就主要跟大家講一下k8s的服務(service)實作,使用場景和一些可能遇到的問題。這邊今天所講的内容是使用的阿裡雲的托管版k8s為例,kube-proxy的mode為iptables。
什麼是服務?
首先請大家思考這樣一個問題,常見的我們通路一個應用是通過域名加端口或者ip加端口的方式來通路。我們知道在k8s中,pod相當于我們的實際業務應用,pod是有一個pod ip的,如果在叢集内我們通過這邊pod ip+pod暴露的端口,那我們就可以通路到這個應用。但是這樣通路有什麼弊端呢?首先我們需要在用戶端固定pod ip位址,那麼當pod重建後,pod ip位址發生變化後,我們就通路不到對應的業務了,又需要手動修改成新的pod ip,這樣會導緻運維起來非常麻煩。其次時,如果一個應用有多個副本,也就是完全相同的pod有多個,我們怎麼能達到負載均衡的效果呢?基于這兩個問題,k8s在設計之初就考慮到了,于是針對pod的代理進行了抽象,定義了一種叫做服務(service)的資源對象,用來代理pod,做pod的負載均衡。由于名稱叫做服務,可能不熟悉的同學會有誤解,将服務了解成後端的應用服務,造成混淆,可以把服務(service)了解成pod的代理,不是pod本身,是兩種k8s的資源對象。所有k8s中對後端pod的請求都可以請求pod上層的服務來實作。那麼請求鍊路就可以了解成下圖了:

服務有哪些種類和各自的使用場景
常見的服務類型
- ClusterIP類型
通過服務的叢集ip來暴露服務,那麼如果我們是叢集内發起對該服務的通路,那麼可以直接通過ClusterIP+端口來通路,注意:這裡說的叢集内是指:pod内和叢集的節點上,都認為是叢集内。
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: sidecar
spec:
clusterIP: 172.21.3.50
ports:
- name: tcp
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-svc
sessionAffinity: None
type: ClusterIP
- NodePort類型
通過節點的ip來暴露對應的服務(service),會在主控端上監聽nodeport這個端口,可以通過主控端ip+nodeport端口通路到service。需要注意的是:NodePort和LoadBalancer的服務可以配置externalTrafficPolicy ,可選值:Cluster和Local。
externalTrafficPolicy:Cluster時,叢集所有節點可以通過nodeport通路,如果externalTrafficPolicy:Local時,隻能在該service關聯的pod所在的節點上通過nodeport進行通路,才能通路到該service。
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: sidecar
spec:
clusterIP: 172.21.3.50
externalTrafficPolicy: Cluster
ports:
- name: tcp
nodePort: 31357
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-svc
sessionAffinity: None
type: NodePort
- LoadBalancer類型
通過外部的一個負載均衡器将外部請求轉發到叢集節點的nodeport端口上,然後請求到服務(service),是以說lb類型的service是基于nodeport的service的,這塊的功能要求雲廠商來實作負載均衡器的配置必須跟随lb類型的service的變化而變化,阿裡雲的k8s已經實作了該功能,lb類型的服務使用的slb的相關配置由cloud-controller-manager控制,不需要手動去配置slb的配置。同樣lb類型的服務可以配置externalTrafficPolicy ,可選值:Cluster和Local。
externalTrafficPolicy:Cluster時,叢集所有節點可以通過nodeport通路,如果externalTrafficPolicy:Local時,隻能在該service關聯的pod所在的節點上通過nodeport進行通路,才能通路到該service。那麼就要求slb的後端伺服器需要根據externalTrafficPolicy政策來調整,目前的政策是這樣的:如果externalTrafficPolicy是Cluster,slb的後端伺服器是所有的worker節點(節點狀态如果是隔離的,該worker不會出現在slb的後端伺服器),如果externalTrafficPolicy是Local,slb的後端伺服器是service關聯的pod所在的ecs(同時隻對worker有效,隔離的worker不會出現在後端伺服器中,master節點也不會出現在slb的後端伺服器中)。
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: sidecar
spec:
clusterIP: 172.21.3.50
externalTrafficPolicy: Cluster
ports:
- name: tcp
nodePort: 31357
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-svc
sessionAffinity: None
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: xx.xx.xx.xx #slb的ip
- ExternalName類型
映射一個外部的域名,傳回一個CNAME記錄。
apiVersion: v1
kind: Service
metadata:
name: baidu
namespace: default
spec:
type: ExternalName
externalName: www.baidu.com
externalTrafficPolicy為Cluster請求鍊路如左圖,為Local時請求鍊路為右圖
其實使用場景也比較簡單,可以根據發起通路的地點決定使用什麼類型的service,如果業務不需要在叢集外部通路,那麼就使用ClusterIP的service類型,如果需要在外部通路,那麼建議使用負載均衡的service類型,同時建議不要在叢集内(叢集節點和pod内)通路負載均衡的service對應的slb位址,請求會被iptables攔截,不會請求到slb上,具體的原因和實作我們在後續再說。一般客戶可能有保留請求源ip的需求,service這一層保留源ip的功能就是通過externalTrafficPolicy:Local來配置的,具體實作我們也後續再說。
服務實作
kube-proxy
要了解k8s服務是如何實作的,首先我們需要先了解一下邊車模式(Sidecar),邊車模式是微服務領域的核心概念,通俗的說法就是自帶通信員,k8s叢集的服務實作也是基于Sidecar模式的。在k8s叢集中,服務的實作實際上是為叢集的每一個節點,部署了一個反向代理Sidecar,所有對叢集服務的通路都被節點上的反向代理轉換成後端pod的通路。
K8S叢集中每個節點上都運作着這樣一個元件kube-proxy,它通過叢集apiserver監聽叢集變化,當有新的服務被建立的時候,kube-proxy将叢集服務的狀态,屬性,配置等翻譯成反向代理的配置。目前kube-proxy的mode主要有三種:userspace、iptables、ipvs,今天隻分析iptalbes的實作方式。
iptables
此處先描述防火牆相關的概念,從實體上區分,可以分為硬體防火牆和軟體防火牆。
硬體防火牆:在硬體級别實作部分防火牆功能,另一部分功能集于軟體實作,性能高,成本高。
軟體防火牆:應用軟體處理邏輯運作于通用硬體平台之上的防火牆,性能低,成本低。
iptables其實并不是真正的防火牆,我們可以把它了解成一個用戶端代理,一個指令行工具,位于使用者空間,使用者通過iptables這個代理,将用的安全設定執行到對應的“安全架構”中,這個“安全架構”才是真正的防火牆,這個架構的名字叫netfilter,netfilter位于核心空間,netfilter/iptables(下文中簡稱iptables)組成了Linux平台下的包過濾防火牆,完成封包過濾、封包重定向和網絡位址轉換(NAT)等功能。
netfilter是一種過濾器架構,我們想象一下,一個過濾器會有哪些組成部分,首先肯定有一個入口和一個出口,然後會有很多“關卡”,每個“關卡”會做很多檢查,每項檢查根據不同的結果做不同的處理。就像我們去做飛機一樣,到機場後需要先過安檢,檢票,識别身份,核實通過才能候機。而每個步驟裡面會有很多的檢查項,需要通過層層檢查才能最後登機。這些“關卡”在iptables中被稱作“鍊”,我們知道,防火牆的作用就是對經過的封包進行比對“規則”,然後執行對應的“動作”,是以當封包經過這些“關卡”的時候必須比對這個“關卡”上的規則,但是“關卡”上可能不止一條規則,是以将這些規則串到一條鍊路上,我們稱為“鍊”。
iptabels中有5條鍊,分别是PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。
從上圖我們可以想象常見的某些場景中,封包的流向:
1、到本機某程序的封包:PREROUTING-->INPUT
2、由本機轉發的封包:PREROUTING-->FORWARD-->POSTROUTING
3、由本機的某程序發出的封包:OUTPUT-->POSTROUTING
同時我們将具有相關功能的規則放在一起稱為表:
- filter表:負責過濾功能,防火牆
- nat表:網絡位址轉換功能
- mangle表:拆解封包,做出修改,并重新封裝
- raw表:關閉nat表上啟用的連接配接追蹤機制
我們可以看下連結清單關系,就是哪些表裡面的規則可以被哪些鍊使用。
raw表:PREROUTING,OUTPUT
mangle表:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
nat表:PREROUTING,OUTPUT,POSTROUTING
filter表:INPUT,FORWARD,OUTPUT
自定義鍊實作反向代理
回歸正題,要實作反向代理,是需要做DNAT ,那麼要求kube-proxy下發的iptables規則實作:将請求叢集服務的ip和端口的資料包,修改成具體的後端的pod ip和端口,根據上面的連結清單關系,我們可以知道,我們需要做nat表來做,那麼可以在PREROUTING,OUTPUT,POSTROUTING三個鍊裡面加入nat規則來改變資料包的源位址和目的位址。在k8s的服務實作中,是在PREROUTING,OUTPUT兩個鍊裡面來做DNAT,如果在發起通路的用戶端pod所在的節點或者通路服務的節點跟服務關聯的pod不在同一個節點上,那麼此時是需要做SNAT的,是在POSTROUTING鍊中來做的。
接下來我們在具體的叢集中來看下具體的情況和分析一下之前的問題(nodeport和保留源ip)的相關實作。
環境配置:
- kube-proxy 代理模式:iptables
- Pod 網絡 CIDR:10.1.0.0/16
- Service CIDR:172.23.0.0/20
- 節點:192.168.2.26和192.168.2.25
這邊建立了三個服務,都關聯到同一個pod,其中nodeport類型服務的externalTrafficPolicy為Cluster,loadbalancer類型服務的externalTrafficPolicy為Local,service關聯的pod在節點192.168.2.26上,pod ip是:10.1.0.16
目前我們是192.168.2.26節點上,我們直接來看下clusterip類型的service的相關iptables規則呢。
我們可以看到KUBE_SERVICE這個鍊是整個服務反向代理的入口鍊:
我們然後看下nginx-clusterip服務的clusterip:172.23.3.114 我們可以如果發起通路的用戶端是目前節點上的pod會增加一個标記:KUBE-MARK-MASQ ,接下來會跳轉到KUBE-SVC-W6AHQRLWJMEKPFAV鍊
我們可以把KUBE-SVC-W6AHQRLWJMEKPFAV鍊看作是某個具體的服務的鍊,然後會跳轉到KUBE-SEP-23XRHPDH6DC7LBN7,這個可以了解為服務對應的endpoint鍊
我們接着看下KUBE-SEP-23XRHPDH6DC7LBN7鍊,我們可以看到這裡面做了兩件事,1、如果發起通路的位址是該服務本身關聯的pod的位址,那麼做一個标記,這樣做的原因是為了發彎卡回環。現象是不允許在service關聯的pod内去通過該服務去通路pod的業務,如果需要通路pod自身的業務,直接使用127.0.0.1即可。2、做DNAT實作反向代理。
總的來說自定義鍊的鍊路是:KUBE_SERVICE-->KUBE-SVC-XXX-->KUBE-SEP-XXX實作DNAT反向代理的。
接下來我們來看下SNAT政策:如果源位址是叢集的pod的位址,目的位址不是叢集的pod的位址,那麼進行SNAT;當源位址不是叢集的pod位址時,目的位址是本機上的pod位址時不進行SNAT,目的位址是其他節點的pod位址時需要進行SNAT。這也就告訴我們應該如何保留源ip不被SNAT掉。當使用負載均衡的服務時,slb轉發請求的節點必須是pod所在的節點,這樣才能實作暴露源ip,這也導緻了slb的後端伺服器政策有所不同,在其他節點(service關聯的pod不在對應的節點上),同時externalTrafficPolicy為Local時,隻有在pod所在的節點上才能通過nodeport通路到。
通過nodeport通路,鍊路有變化:KUBE_SERVICE-->KUBE-NODEPORTS再往後續走。
下面這兩個節點上規則解釋了為什麼:“externalTrafficPolicy:Cluster時,叢集所有節點可以通過nodeport通路,如果externalTrafficPolicy:Local時,隻能在該service關聯的pod所在的節點上通過nodeport進行通路,才能通路到該service”
我們在另一個節點看下對應節點的規則,該節點上沒有運作對應的pod。可以看到負載均衡的service的擴充ip(即slb的ip)會被iptables攔截,請求不會請求到slb。是以不要在叢集内通路service關聯的slb的位址。
至此,k8s的服務實作也分析得差不多了,不過使用iptables實作還是有一些問題的,我們在後續文章再聊一下。