1.故障背景
由於需要部署新環境,於是在阿里雲新建一個ack叢集,部署了業務,結果整晚上的存活探針告警,新叢集接近30個業務pod,整晚上將近50多條存活探針告警,這個結果明顯不正常。
但是檢視所有pod狀態事件全部正常,阿里雲託管的ack也沒有事件異常,第一反應確實是叢集某些引數不對導致這個問題,於是就有了以下過程。
2.環境資訊
這裡首先交代一下環境版本資訊。
軟體
|
版本
|
系統版本
|
Alibaba Cloud Linux release 3 (Soaring Falcon)
|
ack叢集
|
1.28.9
|
3.故障解決
首先,在對比沒有這方面告警的ack叢集的環境下,該新叢集本人僅僅只是手動最佳化了映象的一些引數:比如limit限制,埠範圍等等。
(阿里雲os映象的預設範圍為32768-60999)
本人修改後為5000-65535。而阿里雲ack APIServer提供了ServiceNodePortRange引數範圍為30000~32767,以上明顯衝突覆蓋了。
以下為阿里雲官方解釋:
但是目前為止還不知道為什麼api-server和node節點埠範圍重合會導致探針頻繁失敗,修改node節點埠範圍為32768-60999故障完美解決。
(以上先解決問題,然後開始了漫長的調查出路。)
4.網路原理篇
因為上一次使用版本是停留在1.20版本,沒有對ack版本進行升級,都是湊合用。
目前使用的1.28版本,於是把所有的網路原因重新複習了一遍。如果需要更加深入理解埠衝突的危害,並且在跟某雲提工單的時候不被忽悠,建議看完以下原理篇。
4.1 K8s叢集網路前言
K8s是一個強大的平臺,但它的網路比較複雜,涉及很多概念,例如Pod網路,Service網路,Cluster IPs,NodePort,LoadBalancer和Ingress等等,這麼多概念足以讓新手望而生畏。但是,只有深入理解K8s網路,才能為理解和用好K8s打下堅實基礎。為了幫助大家理解,模仿TCP/IP協議棧,我把K8s的網路分解為四個抽象層,從0到3,除了第0層,每一層都是構建於前一層之上,如下圖所示:
第0層Node節點網路比較好理解,也就是保證K8s節點(物理或虛擬機器)之間能夠正常IP定址和互通的網路,這個一般由底層(公有云或資料中心)網路基礎設施支援。第0層我們假定已經存在,所以不展開。第1到3層網路,分別進行剖析。
本文旨在幫助大家建立K8s網路的概念模型,而不是對底層技術的精確描述。實際我們學技術以應用為主,重要的是快速建立起直觀易懂的概念模型,能夠指導我們正常應用即可,當然理解一定的技術細節也是有幫助的。另外,本文假定讀者對基本的網路技術,ip地址空間和容器技術等有一定的瞭解。
4.2 Pod網路概念模型
Pod相當於是K8s雲平臺中的虛擬機器,它是K8s的基本排程單位。所謂Pod網路,就是能夠保證K8s叢集中的所有Pods(包括同一節點上的,也包括不同節點上的Pods),邏輯上看起來都在同一個平面網路內,能夠相互做IP定址和通訊的網路,下圖是Pod網路的簡化概念模型:
Pod網路構建於Node節點網路之上,它又是上層Service網路的基礎。為了進一步理解Pod網路,我將對同一節點上的Pod之間的網路,以及不同節點上的Pod之間網路,分別進行剖析。
4.2.1 同一節點上的Pod網路
前面提到,Pod相當於是K8s雲平臺中的虛擬機器,實際一個Pod中可以住一個或者多個(大多數場景住一個)應用容器,這些容器共享Pod的網路棧和其它資源如Volume。那麼什麼是共享網路棧?同一節點上的Pod之間如何定址和互通?我以下圖樣例來解釋:
上圖節點上展示了Pod網路所依賴的3個網路裝置,eth0是節點主機上的網路卡,這個是支援該節點流量出入的裝置,也是支援叢集節點間IP定址和互通的裝置。docker0是一個虛擬網橋,可以簡單理解為一個虛擬交換機,它是支援該節點上的Pod之間進行IP定址和互通的裝置。veth0則是Pod1的虛擬網路卡,是支援該Pod內容器互通和對外訪問的虛擬裝置。docker0網橋和veth0網路卡,都是linux支援和建立的虛擬網路裝置。
上圖Pod1內部住了3個容器,它們都共享一個虛擬網路卡veth0。內部的這些容器可以透過localhost相互訪問,但是它們不能在同一埠上同時開啟服務,否則會有埠衝突,這就是共享網路棧的意思。Pod1中還有一個比較特殊的叫pause的容器,這個容器執行的唯一目的是為Pod建立共享的veth0網路介面。如果你SSH到K8s叢集中一個有Pod執行的節點上去,然後執行docker ps,可以看到透過pause命令執行的容器。
Pod的IP是由docker0網橋分配的,例如上圖docker0網橋的IP是172.17.0.1,它給第一個Pod1分配IP為172.17.0.2。如果該節點上再啟一個Pod2,那麼相應的分配IP為172.17.0.3,如果再啟動Pod可依次類推。因為這些Pods都連在同一個網橋上,在同一個網段內,它們可以進行IP定址和互通,如下圖所示
從上圖我們可以看到,節點內Pod網路在172.17.0.0/24這個地址空間內,而節點主機在10.100.0.0/24這個地址空間內,也就是說Pod網路和節點網路不在同一個網路內,那麼不同節點間的Pod該如何IP定址和互通呢?下一節我們來分析這個問題
4.2.2 不同節點間的Pod網路
現在假設我們有兩個節點主機,host1(10.100.0.2)和host2(10.100.0.3),它們在10.100.0.0/24這個地址空間內。host1上有一個PodX(172.17.0.2),host2上有一個PodY(172.17.1.3),Pod網路在172.17.0.0/16這個地址空間內。注意,Pod網路的地址,是由K8s統一管理和分配的,保證叢集內Pod的IP地址唯一。我們發現節點網路和Pod網路不在同一個網路地址空間內,那麼host1上的PodX該如何與host2上的PodY進行互通?
實際上不同節點間的Pod網路互通,有很多技術實現方案,底層的技術細節也很複雜。為了簡化描述,我把這些方案大體分為兩類,一類是路由方案,另外一類是覆蓋(Overlay)網路方案。
如果底層的網路是你可以控制的,比如說企業內部自建的資料中心,並且你和運維團隊的關係比較好,可以採用路由方案,如下圖所示:
這個方案簡單理解,就是透過路由裝置為K8s叢集的Pod網路單獨劃分網段,並配置路由器支援Pod網路的轉發。例如上圖中,對於目標為172.17.1.0/24這個範圍內的包,轉發到10.100.0.3這個主機上,同樣,對於目標為172.17.0.0/24這個範圍內的包,轉發到10.100.0.2這個主機上。當主機的eth0介面接收到來自Pod網路的包,就會向內部網橋轉發,這樣不同節點間的Pod就可以相互IP定址和通訊。這種方案依賴於底層的網路裝置,但是不引入額外效能開銷。
如果底層的網路是你無法控制的,比如說公有云網路,或者企業的運維團隊不支援路由方案,可以採用覆蓋(Overlay)網路方案,如下圖所示:
所謂覆蓋網路,就是在現有網路之上再建立一個虛擬網路,實現技術有很多,例如flannel/weavenet等等,這些方案大都採用隧道封包技術。簡單理解,Pod網路的資料包,在出節點之前,會先被封裝成節點網路的資料包,當資料包到達目標節點,包內的Pod網路資料包會被解封出來,再轉發給節點內部的Pod網路。這種方案對底層網路沒有特別依賴,但是封包解包會引入額外效能開銷。
4.2.3 CNI簡介
考慮到Pod網路實現技術眾多,為了簡化整合,K8s支援CNI(Container Network Interface)標準,不同的Pod網路技術可以透過CNI外掛形式和K8s進行整合。節點上的Kubelet透過CNI標準介面操作Pod網路,例如新增或刪除網路介面等,它不需要關心Pod網路的具體實現細節。
4.2.4 小結
- K8s的網路可以抽象成四層網路,第0層節點網路,第1層Pod網路,第2層Service網路,第3層外部接入網路。除了第0層,每一層都構建於上一層之上。
- 一個節點內的Pod網路依賴於虛擬網橋和虛擬網路卡等linux虛擬裝置,保證同一節點上的Pod之間可以正常IP定址和互通。一個Pod內容器共享該Pod的網路棧,這個網路棧由pause容器建立。
- 不同節點間的Pod網路,可以採用路由方案實現,也可以採用覆蓋網路方案。路由方案依賴於底層網路裝置,但沒有額外效能開銷,覆蓋網路方案不依賴於底層網路,但有額外封包解包開銷。
- CNI是一個Pod網路整合標準,簡化K8s和不同Pod網路實現技術的整合。
有了Pod網路,K8s叢集內的所有Pods在邏輯上都可以看作在一個平面網路內,可以正常IP定址和互通。但是Pod僅僅是K8s雲平臺中的虛擬機器抽象,最終,我們需要在K8s叢集中執行的是應用或者說服務(Service),而一個Service背後一般由多個Pods組成叢集,這時候就引入了服務發現(Service Discovery)和負載均衡(Load Balancing)等問題
4.3 Service 網路
我們假定在K8s叢集中部署了一個Account-App應用,這個應用由4個Pod(虛擬機器)組成叢集一起提供服務,每一個Pod都有自己的PodIP和埠。我們再假定叢集內還部署了其它應用,這些應用中有些是Account-App的消費方,也就說有Client Pod要訪問Account-App的Pod叢集。這個時候自然引入了兩個問題:
- 服務發現(Service Discovery): Client Pod如何發現定位Account-App叢集中Pod的IP?況且Account-App叢集中Pod的IP是有可能會變的(英文叫ephemeral),這種變化包括預期的,比如Account-App重新發布,或者非預期的,例如Account-App叢集中有Pod掛了,K8s對Account-App進行重新排程部署。
- 負載均衡(Load Balancing):Client Pod如何以某種負載均衡策略去訪問Account-App叢集中的不同Pod例項?以實現負載分攤和HA高可用。
實際上,K8s透過在Client和Account-App的Pod叢集之間引入一層Account-Serivce抽象,來解決上述問題:
- 服務發現:Account-Service提供統一的ClusterIP來解決服務發現問題,Client只需透過ClusterIP就可以訪問Account-App的Pod叢集,不需要關心叢集中的具體Pod數量和PodIP,即使是PodIP發生變化也會被ClusterIP所遮蔽。注意,這裡的ClusterIP實際是個虛擬IP,也稱Virtual IP(VIP)。
- 負載均衡:Account-Service抽象層具有負載均衡的能力,支援以不同策略去訪問Account-App叢集中的不同Pod例項,以實現負載分攤和HA高可用。K8s中預設的負載均衡策略是RoundRobin,也可以定製其它複雜策略。
K8s中為何要引入Service抽象?背後的原理是什麼?後面我將以技術演進視角來解釋這些問題。
4.3.1 服務發現技術演進
DNS域名服務是一種較老且成熟的標準技術,實際上DNS可以認為是最早的一種服務發現技術。
在K8s中引入DNS實現服務發現其實並不複雜,實際K8s本身就支援Kube-DNS元件。假設K8s引入DNS做服務發現(如上圖所示),執行時,K8s可以把Account-App的Pod叢集資訊(IP+Port等)自動註冊到DNS,Client應用則透過域名查詢DNS發現目標Pod,然後發起呼叫。這個方案不僅簡單,而且對Client也無侵入(目前幾乎所有的作業系統都自帶DNS客戶端)。但是基於DNS的服務發現也有如下問題
- 不同DNS客戶端實現功能有差異,有些客戶端每次呼叫都會去查詢DNS服務,造成不必要的開銷,而有些客戶端則會快取DNS資訊,預設超時時間較長,當目標PodIP發生變化時(在容器雲環境中是常態),存在快取重新整理不及時,會導致訪問Pod失效
- DNS客戶端實現的負載均衡策略一般都比較簡單,大都是RoundRobin,有些則不支援負載均衡呼叫。
考慮到上述不同DNS客戶端實現的差異,不在K8s控制範圍內,所以K8s沒有直接採用DNS技術做服務發現。注意,實際K8s是引入Kube-DNS支援透過域名訪問服務的,不過這是建立在CusterIP/Service網路之上,這個我後面會展開。
另外一種較新的服務發現技術,是引入Service Registry+Client配合實現,在當下微服務時代,這是一個比較流行的做法。目前主流的產品,如Netflix開源的Eureka + Ribbon,HashiCorp開源的Consul,還有阿里新開源Nacos等,都是這個方案的典型代表。
在K8s中引入Service Registry實現服務發現也不復雜,K8s自身帶分散式儲存etcd就可以實現Service Registry。假設K8s引入Service Registry做服務發現(如上圖所示),執行時K8s可以把Account-App和Pod叢集資訊(IP + Port等)自動註冊到Service Registry,Client應用則透過Service Registry查詢發現目標Pod,然後發起呼叫。這個方案也不復雜,而且客戶端可以實現靈活的負載均衡策略,但是需要引入客戶端配合,對客戶應用有侵入性,所以K8s也沒有直接採用這種方案。
K8s雖然沒有直接採用上述方案,但是它的Service網路實現是在上面兩種技術的基礎上擴充套件演進出來的。它融合了上述方案的優點,同時解決了上述方案的不足,下節我會詳細剖析K8s的Service網路的實現原理。
4.3.2 K8s的Service網路原理
前面提到,K8s的服務發現機制是在上節講的Service Registry + DNS基礎上發展演進出來的,下圖展示K8s服務發現的簡化原理:
在K8s平臺的每個Worker節點上,都部署有兩個元件,一個叫Kubelet,另外一個叫Kube-Proxy,這兩個元件+Master是K8s實現服務註冊和發現的關鍵。下面我們看下簡化的服務註冊發現流程。
- 首先,在服務Pod例項釋出時(可以對應K8s釋出中的Kind: Deployment),Kubelet會負責啟動Pod例項,啟動完成後,Kubelet會把服務的PodIP列表彙報註冊到Master節點。
- 其次,透過服務Service的釋出(對應K8s釋出中的Kind: Service),K8s會為服務分配ClusterIP,相關資訊也記錄在Master上。
- 第三,在服務發現階段,Kube-Proxy會監聽Master並發現服務ClusterIP和PodIP列表對映關係,並且修改本地的linux iptables轉發規則,指示iptables在接收到目標為某個ClusterIP請求時,進行負載均衡並轉發到對應的PodIP上。
- 執行時,當有消費者Pod需要訪問某個目標服務例項的時候,它透過ClusterIP發起呼叫,這個ClusterIP會被本地iptables機制截獲,然後透過負載均衡,轉發到目標服務Pod例項上。
實際消費者Pod也並不直接調服務的ClusterIP,而是先呼叫服務名,因為ClusterIP也會變(例如針對TEST/UAT/PROD等不同環境的釋出,ClusterIP會不同),只有服務名一般不變。為了遮蔽ClusterIP的變化,K8s在每個Worker節點上還引入了一個KubeDNS元件,它也監聽Master並發現服務名和ClusterIP之間對映關係,這樣, 消費者Pod透過KubeDNS可以間接發現服務的ClusterIP。
注意,K8s的服務發現機制和目前微服務主流的服務發現機制(如Eureka + Ribbon)總體原理類似,但是也有顯著區別(這些區別主要體現在客戶端):
- 首先,兩者都是採用客戶端代理(Proxy)機制。和Ribbon一樣,K8s的代理轉發和負載均衡也是在客戶端實現的,但Ribbon是以Lib庫的形式嵌入在客戶應用中的,對客戶應用有侵入性,而K8s的Kube-Proxy是獨立的,每個Worker節點上有一個,它對客戶應用無侵入。K8s的做法類似ServiceMesh中的邊車(sidecar)做法。
- 第二,Ribbon的代理轉發是穿透的,而K8s中的代理轉發是iptables轉發,雖然K8s中有Kube-Proxy,但它只是負責服務發現和修改iptables(或ipvs)規則,實際請求是不穿透Kube-Proxy的。注意早期K8s中的Kube-Proxy代理是穿透的,考慮到有效能損耗和單點問題,後續的版本就改成不穿透了。
- 第三,Ribbon實現服務名到服務例項IP地址的對映,它只有一層對映。而K8s中有兩層對映,Kube-Proxy實現ClusterIP->PodIP的對映,Kube-DNS實現ServiceName->ClusterIP的對映。
個人認為,對比目前微服務主流的服務發現機制,K8s的服務發現機制抽象得更好,它透過ClusterIP統一遮蔽服務發現和負載均衡,一個服務一個ClusterIP,這個模型和傳統的IP網路模型更貼近和易於理解。ClusterIP也是一個IP,但這個IP後面跟的不是一個服務例項,而是一個服務叢集,所以叫叢集ClusterIP。同時,它對客戶應用無侵入,且不穿透沒有額外效能損耗。
4.3.3 總結
- K8s的Service網路構建於Pod網路之上,它主要目的是解決服務發現(Service Discovery)和負載均衡(Load Balancing)問題。
- K8s透過一個ServiceName+ClusterIP統一遮蔽服務發現和負載均衡,底層技術是在DNS+Service Registry基礎上發展演進出來。
- K8s的服務發現和負載均衡是在客戶端透過Kube-Proxy + iptables轉發實現,它對應用無侵入,且不穿透Proxy,沒有額外效能損耗。
- K8s服務發現機制,可以認為是現代微服務發現機制和傳統Linux核心機制的優雅結合。
有了Service抽象,K8s中部署的應用都可以透過一個抽象的ClusterIP進行定址訪問,並且消費方不需要關心這個ClusterIP後面究竟有多少個Pod例項,它們的PodIP是什麼,會不會變化,如何以負載均衡方式去訪問等問題。但是,K8s的Service網路只是一個叢集內可見的內部網路,叢集外部是看不到Service網路的,也無法直接訪問。而我們釋出應用,有些是需要暴露出去,要讓外網甚至公網能夠訪問的,這樣才能對外提供服務。K8s如何將內部服務暴露出去?
4.4 外部接入網路
在講到K8s如何接入外部流量的時候,大家常常會聽到NodePort,LoadBalancer和Ingress等概念,這些概念都是和K8s外部流量接入相關的,它們既是不同概念,同時又有關聯性。下面我們分別解釋這些概念和它們之間的關係。
4.4.1 NodePort
先提前強調一下,NodePort是K8s將內部服務對外暴露的基礎,後面的LoadBalancer底層有賴於NodePort。
之前我們講了K8s網路的4層抽象,Service網路在第2層,節點網路在第0層。實際上,只有節點網路是可以直接對外暴露的,具體暴露方式要看資料中心或公有云的底層網路部署,但不管採用何種部署,節點網路對外暴露是完全沒有問題的。那麼現在的問題是,第2層的Service網路如何透過第0層的節點網路暴露出去?我們可以回看一下K8s服務發現的原理圖,如下圖所示,然後不妨思考一下,K8s叢集中有哪一個角色,即掌握Service網路的所有資訊,可以和Service網路以及Pod網路互通互聯,同時又可以和節點網路打通?
答案是Kube-Proxy。上一篇我們提到Kube-Proxy是K8s內部服務發現的一個關鍵元件,事實上,它還是K8s將內部服務暴露出去的關鍵元件。Kube-Proxy在K8s叢集中所有Worker節點上都部署有一個,它掌握Service網路的所有資訊,知道怎麼和Service網路以及Pod網路互通互聯。如果要將Kube-Proxy和節點網路打通(從而將某個服務透過Kube-Proxy暴露出去),只需要讓Kube-Proxy在節點上暴露一個監聽埠即可。這種透過Kube-Proxy在節點上暴露一個監聽埠,將K8s內部服務透過Kube-Proxy暴露出去的方式,術語就叫NodePort(顧名思義,埠暴露在節點上)。下圖是透過NodePort暴露服務的簡化概念模型
如果我們要將K8s內部的一個服務透過NodePort方式暴露出去,可以將服務釋出(kind: Service)的type設定為NodePort,同時指定一個30000~32767範圍內的埠。服務釋出以後,K8s在每個Worker節點上都會開啟這個監聽埠。這個埠的背後是Kube-Proxy,當K8s外部有Client要訪問K8s叢集內的某個服務,它透過這個服務的NodePort埠發起呼叫,這個呼叫透過Kube-Proxy轉發到內部的Servcie抽象層,然後再轉發到目標Pod上。Kube-Proxy轉發以及之後的環節,可以和上面的《Service網路》的內容對接起來。注意,為了直觀形象,上圖的Service在K8s叢集中被畫成一個獨立元件,實際是沒有獨立Service這樣一個元件的,只是一個抽象概念,如果要理解這個抽象的底層實現細節,可以回頭看上一圖的K8s服務發現原理,或者回到上面的《Service網路》。
4.4.2 LoadBalancer
上面我們提到,將K8s內部的服務透過NodePort方式暴露出去,K8s會在每個Worker節點上都開啟對應的NodePort埠。邏輯上看,K8s叢集中的所有節點都會暴露這個服務,或者說這個服務是以叢集方式暴露的(實際支援這個服務的Pod可能就分佈在其中有限幾個節點上,但是因為所有節點上都有Kube-Proxy,所以所有節點都知道該如何轉發)。既然是叢集,就會涉及負載均衡問題,誰負責對這個服務的負載均衡訪問?答案是需要引入負載均衡器(Load Balancer)。下圖是透過LoadBalancer,將服務對外暴露的概念模型。
假設我們有一套阿里雲K8s環境,要將K8s內部的一個服務透過LoadBalancer方式暴露出去,可以將服務釋出(Kind: Service)的type設定為LoadBalancer。服務釋出後,阿里雲K8s不僅會自動建立服務的NodePort埠轉發,同時會自動幫我們申請一個SLB,有獨立公網IP,並且阿里雲K8s會幫我們自動把SLB對映到後臺K8s叢集的對應NodePort上。這樣,透過SLB的公網IP,我們就可以訪問到K8s內部服務,阿里雲SLB負載均衡器會在背後做負載均衡。
值得一提的是,如果是在本地開發測試環境裡頭搭建的K8s,一般不支援Load Balancer也沒必要,因為透過NodePort做測試訪問就夠了。但是在生產環境或者公有云上的K8s,例如GCP或者阿里雲K8s,基本都支援自動建立Load Balancer。
4.4.3 Ingress
有了前面的NodePort + LoadBalancer,將K8s內部服務暴露到外網甚至公網的需求就已經實現了,那麼為啥還要引入Ingress這樣一個概念呢?它起什麼作用?
我們知道在公有云(阿里雲/AWS/GCP)上,公網LB+IP是需要花錢買的。我們回看上圖的透過LoadBalancer(簡稱LB)暴露服務的方式,發現要暴露一個服務就需要購買一個獨立的LB+IP,如果要暴露十個服務就需要購買十個LB+IP,顯然,從成本考慮這是不划算也不可擴充套件的。那麼,有沒有辦法只需購買一個(或者少量)的LB+IP,但是卻可以按需暴露更多服務出去呢?答案其實不復雜,就是想辦法在K8內部部署一個獨立的反向代理服務,讓它做代理轉發。谷歌給這個內部獨立部署的反向代理服務起了一個奇怪的名字,就叫 Ingress,它的簡化概念模型如下圖所示:
Ingress就是一個特殊的Service,透過節點的HostPort(80/443)暴露出去,前置一般也有LB做負載均衡。Ingress轉發到內部的其它服務,是透過叢集內的Service抽象層/ClusterIP進行轉發,最終轉發到目標服務Pod上。Ingress的轉發可以基於Path轉發,也可以基於域名轉發等方式,基本上你只需給它設定好轉發路由表即可,功能和Nginx無本質差別。
注意,上圖的Ingress概念模型是一種更抽象的畫法,隱去了K8s叢集中的節點,實際HostPort是暴露在節點上的。
所以,Ingress並不是什麼神奇的東西,首先,它本質上就是K8s叢集中的一個比較特殊的Service(釋出Kind: Ingress)。其次,這個Service提供的功能主要就是7層反向代理(也可以提供安全認證,監控,限流和SSL證書等高階功能),功能類似Nginx。第三,這個Service對外暴露出去是透過HostPort(80/443),可以和上面LoadBalancer對接起來。有了這個Ingress Service,我們可以做到只需購買一個LB+IP,就可以透過Ingress將內部多個(甚至全部)服務暴露出去,Ingress會幫忙做代理轉發。
那麼哪些軟體可以做這個Ingress?傳統的Nginx/Haproxy可以,現代的微服務閘道器Zuul/SpringCloudGateway/Kong/Envoy/Traefik等等都可以。當然,谷歌別出心裁給這個東東起名叫Ingress,它還是做了一些包裝,以簡化對Ingress的操作。如果你理解了原理,那麼完全可以用Zuul或者SpringCloudGateway,或者自己定製開發一個反向代理,來替代這個Ingress。部署的時候以普通Service部署,將type設定為LoadBalancer即可,如下圖所示:
注意,Ingress是一個7層反向代理,如果你要暴露的是4層服務,還是需要走獨立LB+IP方式。
4.4.4 Kubectl Proxy & Port Forward
上面提到的服務暴露方案,包括NodePort/LoadBalancer/Ingress,主要針對正式生產環境。如果在本地開發測試環境,需要對本地部署的K8s環境中的服務或者Pod進行快速除錯或測試,還有幾種簡易辦法,這邊一併簡單介紹下,如下圖所示:
- 辦法一,透過kubectl proxy命令,在本機上開啟一個代理服務,透過這個代理服務,可以訪問K8s叢集內的任意服務。背後,這個Kubectl代理服務透過Master上的API Server間接訪問K8s叢集內服務,因為Master知道叢集內所有服務資訊。這種方式只限於7層HTTP轉發。
- 辦法二,透過kubectl port-forward命令,它可以在本機上開啟一個轉發埠,間接轉發到K8s內部的某個Pod的埠上。這樣我們透過本機埠就可以訪問K8s叢集內的某個Pod。這種方式是TCP轉發,不限於HTTP。
- 辦法三,透過kubectl exec命令直接連到Pod上去執行linux命令,功能類似docker exec。
4.4.5 總結
- NodePort是K8s內部服務對外暴露的基礎,LoadBalancer底層有賴於NodePort。NodePort背後是Kube-Proxy,Kube-Proxy是溝通Service網路、Pod網路和節點網路的橋樑。
- 將K8s服務透過NodePort對外暴露是以叢集方式暴露的,每個節點上都會暴露相應的NodePort,透過LoadBalancer可以實現負載均衡訪問。公有云(如阿里雲/AWS/GCP)提供的K8s,都支援自動部署LB,且提供公網可訪問IP,LB背後對接NodePort。
- Ingress扮演的角色是對K8s內部服務進行集中反向代理,透過Ingress,我們可以同時對外暴露K8s內部的多個服務,但是隻需要購買1個(或者少量)LB。Ingress本質也是一種K8s的特殊Service,它也透過HostPort(80/443)對外暴露。
- 透過Kubectl Proxy或者Port Forward,可以在本地環境快速除錯訪問K8s中的服務或Pod。
- K8s的Service釋出主要有3種type,type=ClusterIP,表示僅內部可訪問服務,type=NodePort,表示透過NodePort對外暴露服務,type=LoadBalancer,表示透過LoadBalancer對外暴露服務(底層對接NodePort,一般公有云才支援)。
5.故障排查過程
經過一整天和阿里雲小哥的battle,阿里雲小哥給出的理由是我的pod使用了nodeport埠範圍覆蓋了api-server導致衝突,等等多個原因,但是追溯到具體原因,阿里小哥無法解答,下午得空的時候去簡單複習了一下網路原理,不管怎麼樣,阿里雲小哥給我的解釋都和原理對不上。
於是晚上再提工單,得到另一個小哥的回覆。
以上大概意思就是,1.22版本以後的k8s,kube-proxy不會監聽nodeport,就是不管你已經開始了什麼埠,是沒有任何檢查的,以上是一個很重要的前提,對應前文開頭為什麼之前公司1.20版本卻不會出現這種問題,現在1.28有這個問題。
然後阿里雲小哥又說我透過svc開啟了nodeport所以導致探針衝突。
所以以上2個阿里雲小哥聊到這裡都給了一個答案,埠衝突會探針失敗,至於追根溯源的去勒戒,他們無法給出答案。
在晚上的時候突然想到,一直思想目的埠的問題,想想探針呢?想到探針是有kubectl發起的,發起的源IP是節點ip,於是馬上去確認路由走向,如下:
在 Kubernetes 中,pod是由 kubelet 負責執行的。具體來說,pod的請求是透過以下過程進行的:
1.kubelet:
- 每個節點上都有一個 kubelet 程序負責,管理該節點上的 Pod 和容器。節點請求是由 kubelet 發起的。
2.Pod IP:
- kubelet 會使用 Pod 的 IP 地址來發起請求。每個 Pod 在建立時都會分配一個獨立的 IP 地址。
3.探針型別:
- HTTP 描述符:kubelet 透過發起 HTTP 請求來檢查容器的健康狀態,使用的是 Pod 的 IP 和指定的埠(例如 8080)。
- TCP 節點:kubelet 會嘗試透過指定的埠進行 TCP 連線,以判斷容器是否正常。
- 命令命令:kubelet 執行指定的命令,並根據命令的退出狀態來判斷容器的健康狀態。
那麼kubectl探針如何去請求探測pod?在 Kubernetes 中,節點請求的源 IP 地址是 kubelet 所在節點的 IP 地址。以下是更詳細的說明:
具體問題
1.探針請求的發起者:
- 節點請求是由每個節點上的 kubelet 發起的。kubelet 是負責管理該節點上所有 Pod 的元件。
2.源IP地址:
- 當 kubelet 發起請求時,它使用 kubelet 所在節點的 IP 地址作為源 IP。這個 IP 地址是節點的網路介面 IP 地址。
- 例如,如果 kubelet 在一個 IP 為192.168.1.10的節點上執行,而該節點上的某個 Pod 的 IP 地址為10.244.1.5,那麼 kubelet 發起節點請求時的源 IP 將是192.168.1.10。
節點請求的源 IP 是 kubelet 所在節點的 IP 地址,而目標 IP 是正在檢查的 Pod 的 IP 地址。這種機制保證了 kubelet 能夠有效地監測每個 Pod 的健康狀態。
總結就是說,如果kubectl 開啟的源埠和和api-server 的埠複用了, API Server 使用了 31000-32000 埠範圍。如果節點上某個程序(如 kubelet 發出的探針請求)隨機選擇的源埠落在 31000-32000 範圍內,這可能與 API Server 埠產生衝突,導致埠爭用。
這種衝突會導致探針失敗,因為源埠已被佔用,探針請求無法正常發出。探針失敗的原因並不是 Pod 的埠,而是 kubelet 發出的探針請求使用的 隨機源埠 與 API Server 的埠產生了衝突。
在回到一晚上50多次的探針告警,30個pod,每10秒就要探測一次,那就是10秒30次,一晚上時間就會探測108000次,埠範圍僅有50000萬不到,所以衝突的可能性就很大了。
6.總結
叢集整晚上探針頻繁失敗的主要原因總結如下:
- 1.22版本以後的k8s,kube-proxy不會監聽nodeport, 如果 net.ipv4.ip_local_port_range 的範圍和 ServiceNodePortRange 存在重疊,由於去掉了監聽 NodePort 的邏輯,應用程式在選用隨機埠的時候就可能選中重疊部分。
- Pod的ip以及埠是獨立的,但是發起探針的kubectl的埠是隨機的,這樣就會造成衝突,從而造成探針失敗。
目前還未遇到叢集節點離線等故障,但是也是機率性的問題,在調整2方面埠時,
需要注意該配置項,在阿里雲等保映象中,保持預設即可避免這個問題。
7.參考連結
https://www.jianshu.com/p/80eb2e9e32db
https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/comparison-between-terway-and-flannel?spm=a2c4g.11186623.0.0.45f31c08mHZ1vv
https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/service-faq?spm=5176.2020520104.console-base_help.dexternal.3fb843ec1SbrsW#023c3ce009sv0