兩種流量方向,一個節點
歡迎
大多數架構圖顯示流量單向流動:客戶端在上方,伺服器在下方,箭頭向下指。但現實中,流量是雙向流動的。
入站(Ingress):外部客戶端透過此路徑存取您的服務。位於網路邊緣的反向代理會終止 TLS、路由請求,並執行存取策略。
出站(Egress):您的服務透過此路徑存取外部服務。例如呼叫支付處理商的 API、擷取 Webhook 目標,或向合作夥伴發送請求。這通常透過正向代理或具有允許清單的 NAT 閘道來實現。
許多架構最初都由單一節點同時處理入站與出站流量。這行得通,直到它不再行得通為止。這種失效模式相當隱蔽,通常直到內部服務數量累積到一定程度後才浮現,並教訓我們一個關於「關注點分離」的重要概念。
完成本課程後,您將理解:
- 為何入站(ingress)與出站(egress)代表根本不同的流量模式,具有不同的擴展軸(scaling axes)與不同的失效模式
- Hairpin NAT(迴針 NAT)以及為何嘗試連線至自身的代理伺服器會失敗
- 架構的分岔點:單一節點如何演變為兩個節點,以及各自隨後獨佔的職責
- 安全隔離的優勢:每一側都可以針對其實際允許的對端進行鎖定
- 如何識別您的單一節點設計已跨越必須進行拆分的閾值
為何不同方向需要不同的工具
同一網路邊界的兩種不同工作負載
入站流量(Ingress traffic)特性:
- 由外部方(廣大的網際網路)發起
- 流量規模隨您的使用者基礎而擴展
- TLS 終止、請求路由、依來源進行速率限制
- 縱深防禦(Defense-in-depth)關注點:DDoS 攻擊、濫用、資料抓取
- 公網 IP 需要接受來自任何人的連線
出站流量(Egress traffic)特性:
- 由您自己的服務發起(一組已知的、數量有限的客戶端)
- 流量規模隨您的服務間調用及外部 API 調用模式而擴展
- 在遠端端點實施來源 IP 允許清單(您擁有一個固定的出站 IP,合作夥伴信任該 IP)
- 縱深防禦的考量:資料外洩、被入侵的內部服務向外發起呼叫
- 應拒絕來自您自己服務以外任何人的連線
關鍵的非對稱性:入站流量(ingress)接受來自全球的流量;而出站流量(egress)僅接受來自您自己服務的流量。將兩者置於同一台機器上,意味著該機器必須同時可被全球存取(為了入站流量)且僅可被您的服務存取(為了出站流量)。滿足其中一種需求的防火牆規則會與另一種需求相衝突。
成長路徑:一個小型專案可以將兩者隱藏在一個 IP 和一個工具之後,因為流量規模小且合作夥伴 IP 允許清單很短。隨著專案成長,這兩種角色之間的摩擦會增加,直到某一天,特定的失敗模式(髮夾彎 NAT,hairpin NAT)迫使兩者分離。
迫使架構分裂的 Bug
一個經過脫敏的故障案例
想像一個在生產環境集群中真實發生的架構分岔點。以下名稱已做更改;但其結構與團隊在實際環境中遇到的情況完全一致。
某組織運行一台位於 203.0.113.5 的單一代理伺服器。它處理入站流量(使用者使用 443 端口)與出站流量(內部服務呼叫外部時使用 1080 端口的 SOCKS5)。內部服務位於私有子網中,並透過位於 203.0.113.5:1080 的該 SOCKS5 代理路由所有出站流量。
託管在同一個 203.0.113.5 後端的一個服務是 api.example.com。公共 DNS 將 api.example.com 解析為 203.0.113.5。
現在,另一個內部服務需要呼叫 api.example.com。其出站路徑如下:
1. 內部服務解析 api.example.com → 203.0.113.5
2. 內部服務透過位於 203.0.113.5:1080 的 SOCKS5 出站代理發送請求
3. 代理嘗試從自身開啟到 203.0.113.5:443 的連線
4. 連線被拒絕。 封包必須離開並重新進入同一個 NAT,但大多數網路堆疊會拒絕此行為。代理伺服器無法透過其自身的公網 IP 與自身建立連線。
這就是髮夾彎 NAT(Hairpin NAT):指封包離開 NAT 後,需要重新進入同一個 NAT 才能到達目的地。若路由層缺乏特殊的髮夾彎支援,封包將被丟棄。
為何問題遲才浮現
在專案初期,所有內部服務要麼透過私有主機名稱(internal-api.local)與其他內部服務通訊,要麼根本沒有呼叫回其組織的公網服務。髮夾彎路徑根本不存在。
後來,一項新功能要求服務 A 呼叫 api.example.com(一個公網主機名稱)。髮夾彎路徑隨即被觸發。結果是連線被拒絕,導致服務中斷。
修復措施僅治標(強制解析器提供 api.example.com 的私有 IP 而非公網 IP)。根本原因在於:單台機器承擔了過多職責。
架構的分岔
一台主機變為兩台
乾淨的解決方案:將代理伺服器拆分為兩台機器。
入口伺服器(公網 IP 203.0.113.5):
- 在 80、443 埠上運行 Caddy / 反向代理
- 公網 DNS 記錄指向此處
- 託管 api.example.com、app.example.com 等網域
出口伺服器(不同的公網 IP 203.0.113.99):
- 在 1080 埠上運行 SOCKS5 / 正向代理
- 防火牆將傳入連線限制為僅允許內部子網 IP
- 內部服務將所有傳出流量路由至此地址
這帶來的好處:
1. 解決髮夾彎(Hairpin)問題。 內部服務呼叫 api.example.com 時,流量經由 203.0.113.99(出口)傳出,然後正常連線至 203.0.113.5(入口,不同的 IP)。由於這兩個 IP 位於不同的機器上,NAT 迴圈問題便消失了。
2. 安全性隔離。 出口伺服器的防火牆可鎖定為僅允許一組小型內部 IP。入口伺服器的防火牆則對外部世界保持開放。兩套獨立的規則集,各自清晰地表達其角色。
3. 獨立擴展。 入口頻寬隨使用者數量擴展;出口頻寬隨內部服務活動擴展。可單獨升級其中一方,而不影響另一方。
4. 故障隔離。 出口配置錯誤不再會導致公開網站中斷。針對公開網站的 DDoS 攻擊不再會耗盡出口頻寬。
5. 更清晰的心智模型。 每台機器只負責一項任務。工程師在思考入口問題時無需考慮出口,反之亦然。
兩個維度,兩項規模化決策
獨立擴展
在拆分之前,任一方向的成長都會對同一台機器造成壓力。拆分之後,每個方向都有其獨立的資源配置。
入站流量規模:隨使用者數量擴展。容量決策位於面向公眾的層級(例如:更多的反向代理副本、更大的虛擬機器、前置 CDN)。頻寬預算依據峰值使用者流量進行計算。
出站流量規模:隨內部服務對外部 API 的呼叫量擴展。通常由 Webhook 送達、支付處理器呼叫或第三方資料擷取所主導。頻寬預算依據內部呼叫模式進行計算。
故障隔離:針對公眾入站流量的 DDoS 攻擊不再會耗盡出站頻寬(這些支付處理器呼叫仍會照常進行)。出站代理程式崩潰不再會導致公眾網站當機(使用者仍可存取網站;僅內部出站呼叫失敗)。
不同的 SLO:入站流量的可用性對使用者至關重要(可見的網站中斷);出站流量的可用性對營運人員至關重要(可能需要較長時間才能偵測到的背景故障)。每一側都可以擁有各自的 SLO。
多個出站伺服器
一旦出站角色成為獨立的機器,下一個顯而易見的舉措就是在負載平衡器後端運行多台出站機器以實現高可用性(HA)。每個新的內部服務都指向出站主機名稱(該名稱解析至負載平衡池),而非指向單一 IP。
與分散式系統的其他部分相同:一旦某個層級實現無狀態化並擁有獨立角色,它就能低成本地快速擴展。
新的合作夥伴整合
您的組織按設計執行入站/出站分離。出站伺服器擁有一個固定的公網 IP(203.0.113.99),您已將其加入三個現有合作夥伴 API(支付處理器、簡訊閘道、郵件服務提供商)的允許清單。
產品團隊希望新增第四個整合:一個 Webhook 交付系統,用於回調全球各地的客戶端點。流量預測:每分鐘 10,000 次呼叫,峰值可達 30,000 次。
為成長中的服務設計網路邊界
綜合分析
您已了解為何入站(ingress)與出站(egress)需要不同的工具、導致實際機群中必須將兩者分離的 Hairpin NAT 故障,以及一旦完成分離後,獨立擴展、安全性隔離與故障隔離所帶來的累積效益。
請應用以上所有四點。
一家中型 SaaS 公司為其使用者運行三個產品子網域(app、api、admin),並擁有四個出站整合(Stripe、Twilio、SendGrid、客戶 Webhook 系統)。目前,所有流量都經過單一代理主機,並使用一個公網 IP。他們開始收到報告,指出當內部服務嘗試呼叫 api.example.com 時,會發生間歇性的 Hairpin 故障。他們希望設計一個永久性的解決方案。
本課程接下來將走向何方
本課程接下來將走向何方
你現在已經見證了分散式系統中最乾淨的關注點分離重構之一:一台機器變成兩台,各自擁有明確的角色,而系統在此過程中順帶獲得了擴展性、安全性與故障隔離的好處。
下一課(cs_distsys_failure_modes_and_blast_radius)將擴展故障隔離的推理。您將閱讀一份經過脫敏處理的 DNS-SERVFAIL 事後分析報告,識別連鎖故障模式,並撰寫以系統為目標而非針對個人的無責備行動項目。
配套課程:geometry_of_ingress_egress_separation 將此分割重新詮釋為二部圖,並探討割點、網路分割,以及圖論對網路邊界的啟示。
做得很好。繼續前進。