戰(zhàn)指南:從ClusterIP到LoadBalancer的坑與排查)
K8S里Service這東西表面上看就是給Pod提供一個(gè)穩(wěn)定的訪問入口真用起來才發(fā)現(xiàn)里面門道不少。從最簡單的ClusterIP到需要對接云廠商的LoadBalancer從寫YAML到排查Endpoints為空每一步都藏著細(xì)節(jié)。這篇總結(jié)我按自己平時(shí)排查問題的思路來寫不按官方文檔的順序背書重點(diǎn)放在你真正會(huì)踩到的坑和API字段背后的邏輯上。1. 為什么需要Service理解了這點(diǎn)后面的坑都好避剛接觸K8S的人經(jīng)常問一個(gè)問題Pod有IP我直接訪問Pod不就行了為什么非要搞個(gè)Service出來這個(gè)問題想清楚后面所有API字段的用途你基本都能自己推導(dǎo)出來。1.1 Pod的IP天生就不靠譜Pod的IP地址不是固定的。Deployment滾動(dòng)更新時(shí)舊的Pod被殺掉新的Pod被創(chuàng)建IP完全換了一輪。就算不滾動(dòng)更新Pod被驅(qū)逐、節(jié)點(diǎn)宕機(jī)、手動(dòng)刪除重建IP都會(huì)變。你不可能在應(yīng)用配置里寫死一個(gè)Pod的IP它今天能用明天就404了。更麻煩的是副本擴(kuò)容。一個(gè)Deployment起了三副本三個(gè)Pod各自有獨(dú)立IP客戶端要訪問哪個(gè)如果讓客戶端自己維護(hù)這三個(gè)IP的列表等于把負(fù)載均衡的邏輯踢給了調(diào)用方這在微服務(wù)架構(gòu)里根本不可行。Service正是為了解決這兩個(gè)核心問題而生的給一組動(dòng)態(tài)變化的Pod提供一個(gè)固定的虛擬IPClusterIP同時(shí)在這組Pod之間做負(fù)載均衡。從API設(shè)計(jì)的角度看Service本質(zhì)上是穩(wěn)定入口 動(dòng)態(tài)后端的抽象層。1.2 Service在K8S網(wǎng)絡(luò)架構(gòu)中的角色K8S集群里的網(wǎng)絡(luò)流量路徑大致是客戶端 - Service(ClusterIP) - kube-proxy規(guī)則 - Pod IP。Service對象本身不干活真正轉(zhuǎn)發(fā)流量的是每個(gè)節(jié)點(diǎn)上的kube-proxy組件現(xiàn)在也有其他實(shí)現(xiàn)但主流還是這個(gè)。kube-proxy通過監(jiān)聽API Server上Service和Endpoints的變化動(dòng)態(tài)維護(hù)節(jié)點(diǎn)上的轉(zhuǎn)發(fā)規(guī)則。你可以把Service理解成一個(gè)聲明Endpoints是它對應(yīng)的后端列表kube-proxy是把聲明落到實(shí)處的執(zhí)行者。這個(gè)三層分離的設(shè)計(jì)是理解Service所有行為的鑰匙Service的YAML寫得不完整kube-proxy就不知道該往哪兒轉(zhuǎn)發(fā)Endpoints沒生成Service就是一個(gè)空殼訪問它只會(huì)得到連接拒絕。我實(shí)際排查中遇到的所有Service不通的問題幾乎都能歸因到這三層中某一層出了問題。先檢查Service有沒有選到Pod再看Endpoints是否生成最后才懷疑kube-proxy這個(gè)排查順序能省下大量時(shí)間。2. Service API對象拆解核心字段逐個(gè)過K8S所有對象都是聲明式APIService也不例外。一個(gè)完整的Service定義包含apiVersion、kind、metadata、spec幾個(gè)部分最關(guān)鍵的是spec。我把核心字段按重要程度排個(gè)序每個(gè)都說說它的作用和容易出問題的細(xì)節(jié)。2.1 apiVersion和kind不能寫錯(cuò)Service的apiVersion是v1kind是Service這是最基礎(chǔ)的信息。有人會(huì)覺得這有什么好說的但我見過有人把a(bǔ)piVersion寫成apps/v1直接把創(chuàng)建請求打回來。這倆字段是API Server的路由信息告訴它你要?jiǎng)?chuàng)建什么類型的對象版本不對就是404。apiVersion: v1 kind: Service metadata: name: my-service namespace: default spec: selector: app: my-app ports: - port: 80 targetPort: 80802.2 metadata里容易被忽略的注意事項(xiàng)metadata里name是必填的并且在namespace內(nèi)必須唯一。namespace如果沒寫默認(rèn)就是default。這里有個(gè)實(shí)際開發(fā)中常見的坑如果你是用kubectl create -f方式創(chuàng)建YAML里沒寫namespace對象會(huì)進(jìn)default但你的Deployment可能部署在別的namespaceselector選不到Service就永遠(yuǎn)沒有Endpoints。另外metadata里可以加annotations有些云廠商的LoadBalancer類型Service會(huì)通過annotations來控制負(fù)載均衡器的具體行為比如是否分配公網(wǎng)IP、帶寬上限等。這塊每個(gè)云廠商的鍵名都不同用到的時(shí)候需要查對應(yīng)廠商的文檔。2.3 spec.selector決定后端Pod列表的關(guān)鍵spec.selector是Service和Pod之間的連接器。kube-proxy和API Server的Endpoints控制器會(huì)拿你的selector和集群里所有Pod的labels做匹配匹配上的PodIP會(huì)進(jìn)入Endpoints列表。這里有幾個(gè)關(guān)鍵點(diǎn)selector里的每個(gè)label都必須出現(xiàn)在Pod上是AND的關(guān)系多寫一個(gè)不存在的label直接導(dǎo)致選不到任何PodPod的labels是可以在運(yùn)行中修改的一旦修改導(dǎo)致不再匹配selector它的IP會(huì)立刻從Endpoints里被剔除已有連接會(huì)斷開。我在實(shí)際項(xiàng)目里踩過最深的坑是selector寫對了Pod labels看起來也對但就是選不上。最后發(fā)現(xiàn)原因是Pod所在的namespace和Service不一致Endpoints控制器只在同一個(gè)namespace內(nèi)做匹配。每個(gè)namespace是獨(dú)立的隔離域Service不能跨namespace選Pod。2.4 spec.ports端口映射的完整理解ports字段是數(shù)組可以配置多個(gè)端口。每個(gè)端口有三個(gè)核心配置項(xiàng)port、targetPort、protocol。portService對外暴露的端口ClusterIP上監(jiān)聽的端口客戶端訪問的就是這個(gè)端口。targetPort流量轉(zhuǎn)發(fā)到Pod時(shí)使用的端口。注意targetPort可以不填默認(rèn)等于port。如果Pod里的容器監(jiān)聽的是8080而你想讓Service的端口是80就得顯式寫targetPort: 8080。protocol協(xié)議類型支持TCP、UDP、SCTP默認(rèn)是TCP。UDP和TCP不能混用同一個(gè)Service端口不能同時(shí)監(jiān)聽TCP和UDP這是很多新手會(huì)犯的錯(cuò)誤。targetPort還有一個(gè)明細(xì)用法可以用命名端口named port。如果你的Pod定義了命名端口Service里可以直接引用名字好處是改端口號時(shí)不用改Service的YAML。舉個(gè)例子# Pod里的containerPort定義 spec: containers: - name: app ports: - name: http containerPort: 8080 # Service引用命名端口 spec: ports: - port: 80 targetPort: http2.5 spec.type一個(gè)字段決定四種工作模式type字段是Service最核心的行為開關(guān)支持ClusterIP、NodePort、LoadBalancer、ExternalName四種取值。默認(rèn)是ClusterIP。很多人在這一步就迷糊了不知道選哪種類型也不知道每種類型在實(shí)際環(huán)境中到底長什么樣。我的經(jīng)驗(yàn)是按流量來源來判斷。集群內(nèi)部訪問或者用于微服務(wù)間調(diào)用用ClusterIP就夠了這也是默認(rèn)選項(xiàng)。需要集群外部訪問但沒有現(xiàn)成的負(fù)載均衡器用NodePort在每個(gè)節(jié)點(diǎn)上開一個(gè)端口。部署在云平臺(tái)上需要自動(dòng)創(chuàng)建云負(fù)載均衡器用LoadBalancer。只是想給集群外部的服務(wù)做一個(gè)DNS別名用ExternalName它甚至不創(chuàng)建ClusterIP和Endpoints。3. 四種Service類型怎么選端到端配置實(shí)例這一節(jié)把每種類型都過一遍每個(gè)都給出完整配置和實(shí)際使用場景能直接抄作業(yè)。3.1 ClusterIP默認(rèn)也是用得最多的apiVersion: v1 kind: Service metadata: name: my-app-svc namespace: prod spec: type: ClusterIP selector: app: my-app env: prod ports: - name: http port: 80 targetPort: 8080 protocol: TCP這個(gè)Service創(chuàng)建后會(huì)分配一個(gè)ClusterIP比如10.96.88.88在集群內(nèi)部所有節(jié)點(diǎn)上都能通。訪問http://10.96.88.88:80kube-proxy會(huì)在選中的Pod里挑一個(gè)做轉(zhuǎn)發(fā)。注意幾個(gè)細(xì)節(jié)ClusterIP有默認(rèn)的IP范圍通常是10.96.0.0/12具體看你的kube-apiserver啟動(dòng)參數(shù)。如果你在配置里用了clusterIP: None這會(huì)讓Service變成headless service不分配ClusterIPDNS直接返回后端Pod的IP列表。headless service在需要直連Pod的場景比如數(shù)據(jù)庫主從或需要客戶端自己做負(fù)載均衡的場景下很有用。集群內(nèi)訪問Service通常不走ClusterIP而是走DNS。K8S內(nèi)置的DNS服務(wù)CoreDNS會(huì)自動(dòng)為Service生成DNS記錄格式是service-name.namespace.svc.cluster.local。同namespace下可以直接用Service名訪問跨namespace要帶上namespace名再拼完整域名。這也是微服務(wù)之間互相調(diào)用的標(biāo)準(zhǔn)方式。3.2 NodePort外部訪問的入門方案apiVersion: v1 kind: Service metadata: name: my-app-nodeport namespace: prod spec: type: NodePort selector: app: my-app env: prod ports: - port: 80 targetPort: 8080 nodePort: 30080NodePort是在ClusterIP基礎(chǔ)上在每個(gè)節(jié)點(diǎn)上額外開一個(gè)端口默認(rèn)范圍30000-32767。訪問方式變成http://任意節(jié)點(diǎn)IP:30080流量會(huì)從節(jié)點(diǎn)端口進(jìn)來轉(zhuǎn)發(fā)到ClusterIP再轉(zhuǎn)發(fā)到Pod。nodePort字段可以手動(dòng)指定也可以不寫讓K8S自動(dòng)分配一個(gè)空閑端口。手動(dòng)指定的好處是方便配置防火墻白名單和負(fù)載均衡器監(jiān)聽規(guī)則壞處是有可能被占用創(chuàng)建時(shí)API Server會(huì)檢查。使用NodePort有幾個(gè)實(shí)際注意點(diǎn)。節(jié)點(diǎn)IP得能從外部訪問到如果你有多套環(huán)境共用機(jī)器要規(guī)劃好端口別沖突。另外nodePort默認(rèn)范圍是可以調(diào)的在kube-apiserver啟動(dòng)參數(shù)里改--service-node-port-range但一般建議保持標(biāo)準(zhǔn)范圍避免外部設(shè)備不識(shí)別高位端口。NodePort適合什么場景本地開發(fā)、小規(guī)模集群、不想額外引入負(fù)載均衡器的場景。生產(chǎn)環(huán)境你要是直接用NodePort暴露到公網(wǎng)我見過不少但更常見的做法是NodePort配合外部的負(fù)載均衡器比如Nginx、HAProxy使用外部LB轉(zhuǎn)發(fā)到多個(gè)節(jié)點(diǎn)的NodePort。3.3 LoadBalancer云環(huán)境標(biāo)準(zhǔn)姿勢apiVersion: v1 kind: Service metadata: name: my-app-lb namespace: prod annotations: # 云廠商自定義的注解用于配置LB的具體參數(shù) # 具體鍵名因云廠商而異 spec: type: LoadBalancer selector: app: my-app env: prod ports: - port: 80 targetPort: 8080在云平臺(tái)上創(chuàng)建LoadBalancer類型的Service云廠商的控制器會(huì)自動(dòng)調(diào)用云API創(chuàng)建一個(gè)負(fù)載均衡實(shí)例并且自動(dòng)配置好監(jiān)聽器和后端轉(zhuǎn)發(fā)規(guī)則。你的應(yīng)用等于直接擁有了一個(gè)可以從公網(wǎng)訪問的入口。注意LoadBalancer其實(shí)是NodePort的超集它內(nèi)部仍然會(huì)分配nodePort云LB會(huì)綁定到節(jié)點(diǎn)的NodePort上而不是直接綁定Pod。這意味著每個(gè)負(fù)載均衡Service額外的nodePort也會(huì)被分配只是在HostPort層面占用。如果你的集群是自建的比如裸金屬部署的K8S沒有云廠商的控制器LoadBalancer類型創(chuàng)建完會(huì)一直卡在Pending狀態(tài)需要安裝MetalLB這類實(shí)現(xiàn)。LoadBalancer的一個(gè)重要限制是每個(gè)Service都會(huì)創(chuàng)建一個(gè)獨(dú)立負(fù)載均衡實(shí)例成本和時(shí)延都得考慮。多個(gè)服務(wù)更經(jīng)濟(jì)的方式是用Ingress一個(gè)入口轉(zhuǎn)發(fā)到多個(gè)Service那是另一個(gè)話題這里不展開。3.4 ExternalName特殊但偶爾很管用apiVersion: v1 kind: Service metadata: name: external-db namespace: prod spec: type: ExternalName externalName: db.example.comExternalName是一個(gè)輕量級Service它不創(chuàng)建ClusterIP也沒有Endpoints只是在CoreDNS里配置了一條CNAME記錄。當(dāng)你在集群內(nèi)訪問external-db.prod.svc.cluster.local時(shí)DNS解析會(huì)跳到db.example.com。它可以用來把外部服務(wù)的域名包裝成K8S內(nèi)部的Service名這樣應(yīng)用配置里不需要硬編碼外部域名改起來方便。也可以用來對接一些不在集群內(nèi)的舊系統(tǒng)或者做服務(wù)遷移時(shí)的兼容層。需要注意ExternalName只支持DNS解析如果你要訪問的外部服務(wù)需要走HTTP并有路徑路由的需求ExternalName可能不夠用需要自己再包一層代理。3.5 四種類型的選擇對照我用一張表總結(jié)了下四種類型的關(guān)鍵差異方便你選型時(shí)不糾結(jié)。類型是否分配ClusterIP是否支持集群外訪問典型場景注意事項(xiàng)ClusterIP是否集群內(nèi)微服務(wù)間調(diào)用默認(rèn)類型集群外不可直達(dá)NodePort是是通過節(jié)點(diǎn)IP端口簡單的外部訪問測試、小規(guī)模服務(wù)端口范圍30000-32767受節(jié)點(diǎn)IP可達(dá)性影響LoadBalancer是是通過云負(fù)載均衡器云上生產(chǎn)環(huán)境的公網(wǎng)入口依賴云廠商的控制器或MetalLBExternalName否取決于外部目標(biāo)訪問集群外部已有服務(wù)只做DNS CNAME無流量轉(zhuǎn)發(fā)能力選型思路就一句集群內(nèi)訪問用ClusterIP需要外部訪問但沒有云資源用NodePort云上有LB需求用LoadBalancer訪問外部服務(wù)用ExternalName。4. 實(shí)操全流程從編寫YAML到調(diào)試驗(yàn)證理論說完了實(shí)際動(dòng)手的時(shí)候你得有一套標(biāo)準(zhǔn)的操作流程。一個(gè)Service從無到有到驗(yàn)證通過整個(gè)流程包含四步確認(rèn)后端Pod running、編寫Service YAML、創(chuàng)建Service并檢查Endpoints、實(shí)際驗(yàn)證訪問。4.1 確認(rèn)后端Pod的狀態(tài)和labelsService不是憑空出現(xiàn)的它的selector必須能匹配到Pod所以第一步是確認(rèn)Deployment或者Pod已經(jīng)正常運(yùn)行并且labels是你預(yù)期的那一套。# 查看命名空間下所有Pod及其labels kubectl -n prod get pods --show-labels輸出里會(huì)顯示每Pod的labels比如appmy-app,envprod。檢查labels時(shí)不要漏看HEAD部分有的Pod創(chuàng)建時(shí)label很多你只摘出自己需要的兩三對來匹配就行。還有人可能遇到這種情況Pod狀態(tài)顯示Running但通過Service訪問時(shí)一直超時(shí)。排查一圈發(fā)現(xiàn)Pod起來了但容器里的進(jìn)程根本沒監(jiān)聽targetPort。Service能配置對Pod能匹配到但轉(zhuǎn)發(fā)后沒用因?yàn)榻】禉z查只到容器層面沒到端口層面。做個(gè)端口連通性測試很有必要。# 進(jìn)入集群內(nèi)一個(gè)Pod測試后端端口是否真正可用 kubectl -n prod exec -it any-pod -- curl http://某個(gè)Pod的IP:80804.2 編寫Service YAML的通用模板我習(xí)慣用模板來避免因?yàn)楣P誤出問題。一份最通用的Service YAML長這樣apiVersion: v1 kind: Service metadata: name: app-svc namespace: prod labels: app: app-svc spec: type: ClusterIP selector: app: my-app env: prod ports: - name: http port: 80 # Service暴露的端口 protocol: TCP targetPort: 8080 # 必須與容器實(shí)際監(jiān)聽端口一致 # 如果需要外部訪問按需調(diào)整type和對應(yīng)配置保存為svc.yaml后執(zhí)行kubectl apply -f svc.yamlapply是聲明式的如果Service已存在則更新如果不存在則創(chuàng)建。用kubectl create也可以但它要求目標(biāo)對象當(dāng)前不存在在CI/CD腳本里我個(gè)人更推薦apply。4.3 創(chuàng)建后的驗(yàn)證三部曲創(chuàng)建完Service不能直接走人至少做三件事驗(yàn)證。第一步看Service基本信息kubectl -n prod get svc app-svc -o wide重點(diǎn)關(guān)注EXTERNAL-IP列和PORT(S)列。ClusterIP類型的EXTERNAL-IP是None這正常。LoadBalancer類型如果EXTERNAL-IP一直Pending就要檢查云廠商控制器是否正常。第二步檢查Endpointskubectl -n prod get endpoints app-svcEndpoints列表有IP說明Selector正確選了Pod。如果顯示none說明后端沒匹配上優(yōu)先檢查namespace和labels。這里我要強(qiáng)調(diào)一下Endpoints對象是Service的實(shí)際后端狀態(tài)它是由Endpoints控制器根據(jù)Selector自動(dòng)生成的不要手動(dòng)去改Endpoints雖然有API但改了會(huì)被控制器回填。第三步做實(shí)際的連通性測試。在集群內(nèi)任意節(jié)點(diǎn)或Pod里測試# 從集群內(nèi)Pod訪問Service kubectl -n prod exec -it any-pod -- curl http://app-svc.prod.svc.cluster.local如果用的是NodePort在節(jié)點(diǎn)上直接測curl http://nodeIP:30080三步走完Service通了基本就是真通了。4.4 會(huì)話保持配置的細(xì)節(jié)Stateful應(yīng)用需要同一客戶端的請求都落到同一Pod上這就是會(huì)話保持。Service層面通過spec.sessionAffinity字段控制支持None和ClientIP兩種。spec: sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 10800配置成ClientIP后kube-proxy會(huì)根據(jù)客戶端來源IP做哈希同一個(gè)IP的請求會(huì)盡量轉(zhuǎn)發(fā)到同一個(gè)Pod。timeoutSeconds控制會(huì)話保持的超時(shí)時(shí)間默認(rèn)10800秒即3小時(shí)。這里有個(gè)明顯的副作用需要意識(shí)到如果你在集群外部前面還有一層負(fù)載均衡器做NAT或者代理所有客戶端IP都變成LB的IP那會(huì)話保持就會(huì)失效所有流量都哈希到同一個(gè)Pod上完全喪失負(fù)載均衡能力。解決方案是用Ingress層的會(huì)話保持或應(yīng)用層Session而不要依賴Service的ClientIP。5. 常見問題與排查技巧實(shí)錄Service問題排查是我日常工作中遇到頻率最高的。把所有遇到過的坑整理了一下按從最常見到最隱蔽排個(gè)序。5.1 Endpoints為空最經(jīng)典的問題癥狀Service創(chuàng)建了訪問不通kubectl get endpoints顯示none。排查步驟確認(rèn)Service和Pod在同一namespace。確認(rèn)selector的labels和Pod的labels完全匹配注意有沒有拼寫錯(cuò)誤、額外的空格、key和value的格式問題。確認(rèn)Pod確實(shí)處于Running狀態(tài)。容器一直CrashLoopBackOff的話Pod可能被重建IP在變Selector匹配上也沒用。檢查Pod是否被調(diào)度到不可達(dá)的節(jié)點(diǎn)上。某些情況下節(jié)點(diǎn)網(wǎng)絡(luò)異常Pod能跑但網(wǎng)絡(luò)不通Endpoints存在但流量到不了。如果以上的沒問題看eventkubectl -n prod describe svc app-svc大部分問題出在第二步我遇到80%的Endpoints為空都是labels寫錯(cuò)或者namespace不一致。5.2 端口配置錯(cuò)誤導(dǎo)致的“可以解析但無法連接”癥狀DNS解析能拿到Service名但請求超時(shí)或connection refused。這種情況先確認(rèn)targetPort。我見過有人把targetPort寫成8080但容器真正監(jiān)聽的是5000流量轉(zhuǎn)過去直接被拒絕。用kubectl -n prod get endpoints app-svc -o yaml查看Endpoints的port字段確認(rèn)和容器實(shí)際監(jiān)聽端口一致。還有一個(gè)容易忽略的點(diǎn)多容器的Pod。一個(gè)Pod里有多個(gè)容器時(shí)targetPort如果不指定容器名kube-proxy會(huì)把流量都轉(zhuǎn)發(fā)到Pod的第一個(gè)容器端口如果你期望轉(zhuǎn)到第二個(gè)容器比如sidecar容器就得使用命名端口并在Pod定義里明確命名不同容器端口。5.3 NodePort類型端口沖突或不可達(dá)癥狀NodePort類型Service創(chuàng)建成功但外部節(jié)點(diǎn)IP端口訪問不了。常見原因有三個(gè)節(jié)點(diǎn)的防火墻/安全組沒放行nodePort范圍。這是最常見的需要檢查云安全組或iptables規(guī)則。訪問的不是運(yùn)行中Pod所在的節(jié)點(diǎn)但NodePort類型是全節(jié)點(diǎn)監(jiān)聽的理論上訪問任意節(jié)點(diǎn)都通。如果只有特定節(jié)點(diǎn)不通檢查那個(gè)節(jié)點(diǎn)的kube-proxy是否正常運(yùn)行。nodePort端口范圍被修改過而客戶端訪問的是默認(rèn)范圍。確認(rèn)服務(wù)實(shí)際分配的端口用kubectl get svc看PORT(S)列最保險(xiǎn)。5.4 會(huì)話保持失效的分布不均問題癥狀配置了sessionAffinity: ClientIP后流量還是分散到不同的Pod。這個(gè)我前面提過如果上游有代理做NAT源IP被隱藏了所有客戶端看起來都是一個(gè)IP哈希就全指向同一個(gè)Pod這會(huì)導(dǎo)致某個(gè)Pod負(fù)載過高??梢詮娜肟诩覺-Forwarded-For并讓Service層識(shí)別但實(shí)際更常用的方案是在應(yīng)用層實(shí)現(xiàn)會(huì)話保持。5.5 LoadBalancer一直Pending癥狀LoadBalancer類型Service創(chuàng)建后EXTERNAL-IP一直顯示Pending。自建集群沒有云廠商的負(fù)載均衡控制器或者云廠商controller-manager異常都會(huì)導(dǎo)致這個(gè)問題??碿loud-provider組件日志能定位。如果是裸金屬集群可以考慮部署MetalLB來提供load balancer的實(shí)現(xiàn)它通過BGP或Layer2模式把NodePort或Pod IP廣播給外部網(wǎng)絡(luò)。5.6 UDP端口超時(shí)和丟包面向UDP的Service排查起來比TCP麻煩得多因?yàn)閁DP沒有握手和連接狀態(tài)丟包了你很難定位。kube-proxy的負(fù)載均衡是基于conntrack的UDP同樣會(huì)被跟蹤但某些場景下conntrack表項(xiàng)老化過快導(dǎo)致UDP包頻繁被分到不同后端造成服務(wù)異常。如果業(yè)務(wù)是UDP且對后端一致性有要求建議對UDP Service關(guān)閉會(huì)話保持相關(guān)配置或者用headless service讓客戶端直接連后端做應(yīng)用層處理。5.7 問題速查表癥狀優(yōu)先排查項(xiàng)次要排查項(xiàng)Endpoints為空selector與labels不匹配namespace不一致、Pod未RunningService DNS可以解析但連接超時(shí)targetPort是否正確Pod容器端口是否真正監(jiān)聽NodePort外部訪問不通安全組/防火墻放行kube-proxy狀態(tài)、端口范圍LoadBalancer一直Pending云廠商控制器狀態(tài)自建集群缺少LB實(shí)現(xiàn)會(huì)話保持不生效上游代理是否做NAT超時(shí)時(shí)間配置UDP服務(wù)偶發(fā)失敗conntrack表狀態(tài)后端Pod是否有UDP監(jiān)聽6. 一些寫在最后的實(shí)操體會(huì)Service看起來簡單但它連接了K8S的多個(gè)核心組件API Server存儲(chǔ)聲明狀態(tài)Endpoints控制器維護(hù)后端列表kube-proxy把規(guī)則下發(fā)到每個(gè)節(jié)點(diǎn)DNS服務(wù)提供域名解析。任何一個(gè)環(huán)節(jié)出問題表現(xiàn)都是Service不通但定位路徑完全不同。我的個(gè)人習(xí)慣是任何Service變更都遵循先確認(rèn)后端Pod再確認(rèn)selector再確認(rèn)端口最后看訪問的順序。遇到通一下不通一下的詭異問題優(yōu)先檢查網(wǎng)絡(luò)層kube-proxy日志、節(jié)點(diǎn)網(wǎng)絡(luò)插件狀態(tài)而不是反復(fù)盯著YAML文件看。YAML寫錯(cuò)會(huì)立刻暴露網(wǎng)絡(luò)問題才是AM隱性的。最后分享一個(gè)小技巧。調(diào)試Service時(shí)別只依賴kubectl get svc多看看kubectl get endpoints和kubectl describe svc。Endpoints是后端真實(shí)狀態(tài)的照妖鏡它有沒有IP一眼就能看出來selector是否匹配成功。比跑到節(jié)點(diǎn)上抓包快得多。掌握這個(gè)習(xí)慣后你會(huì)發(fā)現(xiàn)Service排查的效率能提高一大截。