![]()
一個Pod重啟,IP地址從10.244.1.5變成10.244.2.10。你的微服務(wù)集群里,這樣的地址漂移每天發(fā)生上千次。
谷歌的Kubernetes(容器編排系統(tǒng),簡稱K8s)從2014年開源至今,解決了容器調(diào)度、彈性擴(kuò)縮容、故障自愈,卻把一個最基礎(chǔ)的問題留給了用戶:服務(wù)之間怎么互相找到對方?
這不是配置問題,是架構(gòu)設(shè)計的選擇。理解它,才能理解為什么你的服務(wù)網(wǎng)格(Service Mesh)方案總是過度設(shè)計。
Pod的"房間號"每天都在變
K8s給每個Pod分配獨立IP,這設(shè)計看似優(yōu)雅——每個容器像一棟大樓里的獨立房間,有自己的門牌號。房間之間可以直接通話,無需中轉(zhuǎn)。
但Pod是臨時的。崩潰重啟、滾動更新、水平擴(kuò)縮,都會讓舊Pod消失、新Pod誕生。新Pod拿到新IP,舊地址永久失效。
想象一家咖啡店:周一在101室,周二系統(tǒng)崩潰后重生到205室,周三為了應(yīng)對早高峰同時開了101、205、302三個分店。你的老顧客該去哪找咖啡?
這正是K8s原生網(wǎng)絡(luò)模型的殘酷現(xiàn)實。IP地址不是身份標(biāo)識,只是臨時坐標(biāo)。 用IP直接訪問服務(wù),等于把系統(tǒng)穩(wěn)定性建立在流沙之上。
谷歌的工程師當(dāng)然知道這個問題。他們的解法不是修改Pod模型——那樣會打破K8s的設(shè)計哲學(xué)——而是在之上疊加一層抽象。
Service:大樓前臺的"轉(zhuǎn)分機(jī)"機(jī)制
K8s的Service(服務(wù))本質(zhì)上是一個穩(wěn)定的DNS條目加四層負(fù)載均衡。它不指向具體Pod,而指向一組Label(標(biāo)簽)匹配的Pod集合。
用咖啡店的類比:你不再記房間號,而是撥打"咖啡店轉(zhuǎn)分機(jī)"。前臺系統(tǒng)實時查詢哪些房間掛著"咖啡店"門牌,把電話轉(zhuǎn)過去。101室關(guān)了?自動轉(zhuǎn)205。三個分店都在?輪流分配客流。
這個設(shè)計的精妙之處在于解耦。調(diào)用方只認(rèn)Service名字,不關(guān)心后端拓?fù)洌贿\(yùn)維方隨意擴(kuò)縮、替換Pod,上游無感知。
Service拿到一個ClusterIP(集群內(nèi)部虛擬IP),這個地址比Pod IP穩(wěn)定得多——只要Service對象不被刪除,ClusterIP不變。但注意,ClusterIP只在集群內(nèi)部可達(dá),外部流量需要NodePort或Ingress(入口網(wǎng)關(guān))額外暴露。
這里藏著K8s的第一個"坑":很多團(tuán)隊早期直接用Pod IP做服務(wù)間調(diào)用,或者把Service當(dāng)DNS用卻忽略了緩存問題。CoreDNS(K8s默認(rèn)DNS服務(wù))的默認(rèn)TTL(生存時間)是5秒,意味著客戶端可能緩存過期地址長達(dá)半分鐘。
Endpoints:前臺背后的實時花名冊
Service本身不存儲Pod列表。真正干活的是Endpoints(端點)對象——一個自動維護(hù)的IP:Port列表,由K8s控制平面實時同步。
當(dāng)你創(chuàng)建一個Service,K8s的Endpoint Controller(端點控制器)開始監(jiān)聽所有匹配Label的Pod。Pod Ready(就緒)狀態(tài)變化?Endpoints列表秒級更新。Pod被刪除?立刻從列表剔除。新Pod啟動?加入輪換。
kube-proxy(K8s的網(wǎng)絡(luò)代理組件)在每個節(jié)點運(yùn)行,它把Service的ClusterIP映射到本地iptables(Linux防火墻規(guī)則)或IPVS(虛擬服務(wù)器)規(guī)則。流量命中ClusterIP時,被透明地DNAT(目標(biāo)地址轉(zhuǎn)換)到某個后端Pod。
這個機(jī)制從2014年沿用至今,簡單、可靠、無額外依賴。但它有兩個隱性成本:
第一,網(wǎng)絡(luò)跳數(shù)。流量從客戶端Pod出發(fā),先到本節(jié)點kube-proxy做DNAT,再路由到目標(biāo)節(jié)點,最后進(jìn)入目標(biāo)Pod。跨節(jié)點通信至少多一跳。
第二,擴(kuò)縮容抖動。大規(guī)模集群中,Endpoints對象可能包含數(shù)千個條目。每次Pod變動觸發(fā)全量列表推送,控制平面和網(wǎng)絡(luò)代理的壓力隨規(guī)模線性增長。
2020年,K8s引入EndpointSlice(端點切片)作為替代。把一個大Endpoints對象拆成多個切片,按需分發(fā),更新粒度更細(xì)。但底層邏輯沒變:還是"前臺轉(zhuǎn)分機(jī)"模式。
DNS的陷阱:名字解析不是免費(fèi)的
Service的名字通過DNS暴露,格式是service-name.namespace.svc.cluster.local。這給了開發(fā)者"像訪問網(wǎng)站一樣訪問內(nèi)部服務(wù)"的幻覺。
但DNS在K8s里是多層架構(gòu)。Pod里的應(yīng)用發(fā)起查詢→本地DNS緩存(如nscd或systemd-resolved)→CoreDNS Pod→etcd(K8s的分布式鍵值存儲)中的DNS記錄。
任何一層緩存過期、CoreDNS Pod重啟、etcd網(wǎng)絡(luò)分區(qū),都會導(dǎo)致解析失敗或返回過期IP。更隱蔽的是NDOTS配置:Linux默認(rèn)的DNS搜索域后綴數(shù)量是1,K8s為了支持短域名(直接寫coffee-shop而非全限定名)把它設(shè)成5。這意味著每次DNS查詢可能觸發(fā)5次遞歸嘗試,延遲暴漲。
我見過一個生產(chǎn)事故:某團(tuán)隊的服務(wù)間調(diào)用P99延遲突然從5ms跳到200ms。根因是新部署的Java應(yīng)用使用了默認(rèn)JVM DNS緩存,TTL永不刷新,而上游Service的后端Pod已經(jīng)全換了一遍。應(yīng)用拿著5個過期IP瘋狂重試,超時后 fallback 到下一個,惡性循環(huán)。
解決方案?要么禁用JVM DNS緩存(-Dsun.net.inetaddr.ttl=0),要么改用K8s的Headless Service(無頭服務(wù))直接返回Pod IP讓客戶端自己負(fù)載均衡,要么上Service Mesh接管服務(wù)發(fā)現(xiàn)。
Headless Service:當(dāng)"前臺轉(zhuǎn)分機(jī)"不夠用時
把Service的clusterIP: None,它就變成Headless Service。DNS查詢不再返回虛擬IP,而是直接返回所有后端Pod的A記錄。
這有什么用?
StatefulSet(有狀態(tài)應(yīng)用集)需要它。MySQL主從、Redis Cluster、ZooKeeper——這些系統(tǒng)要求客戶端知道每個節(jié)點的具體身份,不能透明負(fù)載均衡。Headless Service讓DNS返回mysql-0.mysql.default.svc.cluster.local、mysql-1.mysql.default.svc.cluster.local這種可預(yù)測的FQDN(全限定域名),配合穩(wěn)定的網(wǎng)絡(luò)標(biāo)識(Pod名即主機(jī)名),實現(xiàn)有狀態(tài)服務(wù)的身份持久化。
另一個場景是客戶端負(fù)載均衡。某些RPC框架(如gRPC)更愿意自己維護(hù)連接池,根據(jù)實時健康狀態(tài)做智能路由。Headless Service給它們提供"原始素材",把負(fù)載均衡策略從kube-proxy手里拿回來。
代價也很明顯:應(yīng)用層必須處理IP變動。Pod重啟后DNS記錄更新有延遲(默認(rèn)5秒TTL),這段時間客戶端可能連到黑洞。gRPC的Name Resolver(名稱解析器)需要配置serviceConfig做優(yōu)雅處理,否則就是連鎖故障。
ExternalName:給外部世界一個"分機(jī)號"
不是所有依賴都在K8s里。你的MySQL可能是云廠商的托管實例,Redis可能是ElasticCache,支付網(wǎng)關(guān)可能是第三方SaaS。
ExternalName類型的Service把這些外部端點納入K8s的命名體系。創(chuàng)建一個ExternalName Service指向prod-mysql.example.com,集群內(nèi)應(yīng)用就可以用mysql.default.svc.cluster.local訪問它,和訪問內(nèi)部服務(wù)完全一致。
這看似只是語法糖,實則統(tǒng)一了服務(wù)契約。配置管理、監(jiān)控埋點、訪問控制都可以基于Service名字做,無需區(qū)分內(nèi)外。哪天把外部MySQL遷進(jìn)K8s?改Service類型,上游零感知。
但注意:ExternalName只是DNS層面的CNAME記錄,沒有健康檢查、沒有負(fù)載均衡、沒有連接管理。如果prod-mysql.example.com背后只有單IP,它掛了就是掛了,K8s不會幫你切流。
Ingress與Gateway:當(dāng)"分機(jī)號"需要對外開放
ClusterIP只在集群內(nèi)可達(dá)。要讓外部流量進(jìn)來,傳統(tǒng)方案是NodePort(節(jié)點端口)——在每個節(jié)點開固定端口,把流量轉(zhuǎn)發(fā)到Service。端口范圍30000-32767,URL變成http://node-ip:30080,生產(chǎn)環(huán)境幾乎不可用。
Ingress(入口)是K8s的七層路由抽象。你部署一個Ingress Controller(如Nginx Ingress、Traefik),它監(jiān)聽集群外的80/443,根據(jù)Host和Path規(guī)則把流量分發(fā)到不同Service。
Ingress API的設(shè)計堪稱K8s史上最混亂的章節(jié)之一。它只定義了最小公約數(shù):Host、Path、Service后端、TLS終止。高級功能(重寫、限流、認(rèn)證、灰度發(fā)布)全靠各Controller的自定義Annotation(注解),沒有可移植性。
2022年,Kubernetes Gateway API(網(wǎng)關(guān)API)正式發(fā)布,試圖終結(jié)這種混亂。它把路由、后端、策略拆成獨立資源,支持多租戶、多協(xié)議(HTTP/TCP/UDP/gRPC)、更細(xì)粒度的流量控制。
但遷移成本是真實的。Nginx Ingress的生態(tài)積累太深,很多團(tuán)隊寧愿繼續(xù)寫nginx.ingress.kubernetes.io/rewrite-target這種咒語,也不愿擁抱新標(biāo)準(zhǔn)。
Service Mesh:當(dāng)"前臺轉(zhuǎn)分機(jī)"成為瓶頸
回到最初的問題:kube-proxy的DNAT方案,在超大規(guī)模集群中開始吃力。每次Service后端變動,全集群的iptables規(guī)則都要刷新,延遲和CPU消耗不可忽視。
更深層的問題是可觀測性和安全。Service只解決"找到對方",不解決"加密通信""身份認(rèn)證""細(xì)粒度授權(quán)""調(diào)用鏈追蹤"。這些需求催生了Service Mesh(服務(wù)網(wǎng)格):Istio、Linkerd、Consul Connect。
Service Mesh的核心是Sidecar(邊車)代理。每個Pod里除了業(yè)務(wù)容器,再注入一個Envoy(或類似代理)。所有進(jìn)出流量被劫持到Sidecar,它完成服務(wù)發(fā)現(xiàn)、負(fù)載均衡、mTLS(雙向TLS)、指標(biāo)采集、故障注入。
服務(wù)發(fā)現(xiàn)機(jī)制也變了:不再依賴K8s Service和DNS,而是直接對接控制平面(如Istiod)獲取全量端點列表,基于實時健康狀態(tài)做智能路由。這消除了DNS緩存和kube-proxy的中間層,但也引入了新的復(fù)雜度——Sidecar本身的資源消耗、啟動延遲、配置傳播時延。
一個常被忽視的真相:Service Mesh不是K8s服務(wù)發(fā)現(xiàn)的替代,而是增強(qiáng)。 你完全可以保留K8s Service作為"兜底",只在需要高級功能的命名空間啟用Mesh。很多團(tuán)隊的實踐是漸進(jìn)式采用:先讓Mesh接管東西向流量,南北向繼續(xù)走Ingress。
2024年的新變量:K8s原生服務(wù)網(wǎng)格
Istio的維護(hù)者——Google、IBM、Red Hat——在2022年把項目捐贈給CNCF(云原生計算基金會),但社區(qū)對Sidecar模式的抱怨從未停止。每個Pod多一個容器,意味著多30-100MB內(nèi)存、多1-3秒啟動時間、多一層故障點。
2023年,Istio推出Ambient Mesh(環(huán)境網(wǎng)格)模式。Sidecar變成可選,改為在每個節(jié)點運(yùn)行ztunnel(零信任隧道)和waypoint(路徑點代理)。無加密需求的流量走ztunnel直接轉(zhuǎn)發(fā),需要七層處理的才經(jīng)過waypoint。資源開銷大幅降低,但架構(gòu)復(fù)雜度上升。
同期,Cilium(基于eBPF的容器網(wǎng)絡(luò)方案)推出Service Mesh功能,完全無Sidecar。利用Linux內(nèi)核的eBPF(擴(kuò)展伯克利包過濾器)技術(shù),在數(shù)據(jù)平面直接完成負(fù)載均衡、可觀測性、安全策略,無需用戶態(tài)代理。
這些演進(jìn)的共同指向是:服務(wù)發(fā)現(xiàn)正在從"用戶態(tài)代理"回退到"內(nèi)核態(tài)或控制平面直接處理"。 K8s的原始設(shè)計——Service + Endpoints + kube-proxy——在簡單場景依然夠用,但邊界不斷被壓縮。
一個被忽視的替代方案:直接走Pod網(wǎng)絡(luò)
回到Networking 101的類比。K8s的Pod網(wǎng)絡(luò)是"全連通"的——任何Pod可以直接訪問任何Pod的IP,無需NAT。
這意味著,如果你愿意放棄Service的負(fù)載均衡和故障轉(zhuǎn)移,完全可以自己實現(xiàn)服務(wù)發(fā)現(xiàn)。用K8s API Watch(監(jiān)聽)Pod變化,維護(hù)本地端點緩存,直接對Pod IP建連接。
一些高性能場景確實這么做。比如Cassandra、Kafka這類數(shù)據(jù)系統(tǒng),它們有自己的集群成員協(xié)議(Gossip),只需要K8s提供穩(wěn)定的Pod標(biāo)識(Headless Service + StatefulSet),網(wǎng)絡(luò)層完全自建。
更激進(jìn)的方案是跳過K8s網(wǎng)絡(luò),直接用主機(jī)網(wǎng)絡(luò)(hostNetwork: true)或SR-IOV(單根I/O虛擬化)網(wǎng)卡直通。延遲最低,但犧牲了K8s的調(diào)度靈活性和安全隔離。
選型建議:沒有銀彈,只有權(quán)衡
對于25-40歲的技術(shù)決策者,我的建議是分層看待:
小規(guī)模集群(<50節(jié)點,<100服務(wù)):K8s原生Service + CoreDNS足夠。關(guān)注DNS緩存配置,監(jiān)控CoreDNS的轉(zhuǎn)發(fā)延遲和丟包率。
中等規(guī)模(50-500節(jié)點):評估是否需要Headless Service讓客戶端做負(fù)載均衡,或者引入External-DNS把Service同步到外部DNS(如Route53、Cloudflare),簡化跨集群發(fā)現(xiàn)。
大規(guī)模或強(qiáng)合規(guī)場景:Service Mesh是必選項,但不必全盤Istio。Linkerd更輕量,Cilium Service Mesh對eBPF友好團(tuán)隊更自然。關(guān)鍵指標(biāo)是Sidecar資源占比——如果超過業(yè)務(wù)容器的15%,考慮Ambient模式或無Sidecar方案。
混合云/多集群:K8s的Service發(fā)現(xiàn)止步于集群邊界。需要Submariner、Cilium Cluster Mesh或Istio多集群模式打通。這些方案的運(yùn)維復(fù)雜度常被低估,建議從聯(lián)邦控制平面(Federation v2)的教訓(xùn)中吸取經(jīng)驗——不要過早追求"一個平面管理所有集群"。
一個具體的產(chǎn)品細(xì)節(jié):Google GKE(Google Kubernetes Engine)在2023年把Dataplane V2(基于eBPF的網(wǎng)絡(luò)策略和可觀測性)設(shè)為默認(rèn),kube-proxy被完全繞過。這是云廠商層面的優(yōu)化,自建集群的工程師需要評估Cilium或Calico eBPF數(shù)據(jù)平面的成熟度。
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.