兩種流量方向,一個盒子
歡迎
大多數架構圖表顯示流量只有一個方向:客戶端在上方,伺服器在下方,箭頭指向下方。現實中流量有兩種方向。
入口:外部客戶通過這條路徑達到您的服務。您的網絡邊緣的反向代理終止TLS,路由請求並執行訪問策略。
出口:您的服務通過這條路徑達到外部服務。調用支付處理器的API,獲取網絡-hook目標,向夥伴發送請求。通常通過允許清單的前向代理或NAT閘道。
許多架構從一個盒子處理兩個開始。它們運行得好,直到有一天不再如此。失敗模式微妙,只有在內部服務足夠多時才會出現,並且教會了關注分離的重要教訓。
在這堂課的結束時,您將了解到:
- 為什麼入口和出口代表著根本不同的流量模式,具有不同的擴展軸和不同的失敗模式
- Hairpin NAT 和一個試圖連接到自己失敗的代理
- 架構分叉:一個盒子變成兩個,然後每個盒子獨自擁有
- 安全隔離的好處:每個側面都可以鎖定到其真正允許的對手
- 如何識別您的單一盒子設計已經跨越了分裂所必需的門檻
為什麼方向需要不同的工具
兩個不同的負載在一個網絡邊界上
入口流量特點:
- 來自外部方(整個互聯網)
- 量與用戶基數成正比
- TLS終止,請求路由,源端限制
- 深度防禦關注:DDoS,濫用,抓取
- 公共IP需要接受來自任何人的連接
出口流量特點:
- 來自您自己的服務(一個已知的,有限的客戶集)
- 量與服務之間的呼叫模式和外部API呼叫模式成正比
- 源 IP 允許清單 (您有一個固定出bounds IP,夥伴信任)
- 深度防禦關注點:資料外洩、內部服務被攻擊後呼叫出bounds
- 應拒絕來自您自己的服務之連接
關鍵不對稱性:入口接受世界上的流量;出口僅接受您自己的服務流量。將兩者放在同一機器上意味著該機器必須同時從世界(入口)和您自己的服務(出口)接收連接。滿足一方的防火牆規則會對另一方產生阻礙。
成長路線:一個微小的項目可以將兩者都藏在一個IP和一個工具後面,因為流量很小,夥伴IP允許清單也很短。隨著項目的成長,兩個角色的摩擦會增加,一天,特定的失敗模式(hairpin NAT)迫使分離。
那個bug迫使分離
一個清潔的停機故事
想象一個在生產環境中發生的實際架構分叉。以下的名字已經被更改;形狀與實際世界中團隊遇到的形狀相同。
一個組織在 203.0.113.5 上運行單個代理伺服器。它處理入口(對用戶的443端口)和出口(對內部服務呼叫出bounds的1080端口SOCKS5)。內部服務位於私有子網中,並將所有出bounds流量通過該SOCKS5代理 203.0.113.5:1080。
在同一個 203.0.113.5 上主機的服務是 api.example.com。公共DNS將 api.example.com 解析為 203.0.113.5。
現在,另一個內部服務需要呼叫 api.example.com。其出bounds路徑為:
1. 內部服務解析 api.example.com → 203.0.113.5
2. 內部服務將請求通過SOCKS5 egress代理在 203.0.113.5:1080 發送
3. 代理嘗試從自身到 203.0.113.5:443 打開一個連接
4. 連接被拒絕。該包必須exit並重新入同一個NAT,許多網絡堆棧將拒絕。代理無法通過其公眾IP將自己連接到自身。
這是 hairpin NAT:一個從 NAT 出口的包,需要通過相同的 NAT 才能到達其目的地。沒有在路由層面特別支持 hairpin,該包將會被丟棄。
為什麼它會在後面出現
在項目的早期,每個內部服務要么通過私有主機名(internal-api.local)與其他內部服務通信,要么不叫回自己的組織的公共服務。hairpin 路徑根本不存在。
然後,出現了一個新功能,服務 A 需要呼叫 api.example.com(一個公共主機名)。hairpin 路徑被激活。連接被拒絕。出現了中斷。
修復了症狀(強制解析器給出 api.example.com 的私有 IP,而不是公共 IP)。根據原因:一個單獨的盒子在做太多的工作。
架構分叉
一個盒子變成兩個
清潔的修復:將代理分成兩個機器。
Ingress 服務器(公共 IP 203.0.113.5):
- Caddy / 反向代理,埠 80, 443
- 公共 DNS 記錄指向這裡
- 主持 api.example.com,app.example.com 等
Egress 服務器(不同的公共 IP 203.0.113.99):
- SOCKS5 / 直接代理,埠 1080
- 防火牆限制進入連接至內部子網 IP 的連接
- 內部服務器將所有出bounds 連接通過這個地址進行路由
這買到了什麼:
1. Hairpin 已解決。 內部服務呼叫 api.example.com 會通過 203.0.113.99(egress)路由,然後正常連接到 203.0.113.5(ingress,不同的 IP)。NAT 循環消失,因為這兩個 IP 住在不同的機器上。
2. 安全隔離。 egress 服務器的防火牆可以鎖定在一個有限的內部 IP 集合上。ingress 服務器的防火牆保持對外世界開放。兩個獨立的規則集,各自清晰地表達一個角色。
3. 獨立擴展。 Ingress 的帶寬與用戶數量成比例;egress 的帶寬與內部服務活動成比例。升級一個沒有觸及另一個。
4. 故障隔離。 如果 egress 配置錯誤,公共站點不會被破壞。對公共站點進行 DDoS 攻擊也不會讓 egress 帶寬枯竭。
5. 更清晰的mental模型。 每台机器都有一项工作。工程师不用考虑egress,而是reason about ingress concerns & vice versa.
两个轴,两个-sizing决策
独立扩展
在分裂之前,两者方向的增长都压力在同一台机器上。在分裂之后,每个方向都有自己的provisioning。
Ingress sizing: 根据用户规模扩展。容量决策位于公共面向层(更多的反向代理实例,更大的VM,CDN在前面)。带宽预算根据峰值用户流量计算。
Egress sizing: 根据内部服务到外部API调用量扩展。通常由webhook传递,支付处理器调用或第三方数据抓取主导。带宽预算根据内部调用模式计算。
故障隔离: 公共ingress上的DDoS不再吃egress带宽(那些支付处理器调用仍然通过)。egress proxy失效不再导致公共站点下线(用户仍然可以访问站点;只有内部出bounds调用失败)。
不同的SLO: ingress可用性对用户重要(可见的站点中断);egress可用性对操作员重要(后台失败可能需要更长时间检测)。每个侧都可以承担自己的SLO。
多个egress服务器
一旦egress角色成为自己的机器,下一个明显的动作就是在负载均衡器后面运行几个egress机器以实现HA。每个新的内部服务都指向egress主机名(哪个解析为负载均衡池)而不是单个IP。
与分布式系统的其余部分一样:一旦一层无状态且有自己角色的,它便可以廉价地增加。
一个新的合作伙伴集成
您的组织按照设计运行ingress / egress分裂。egress服务器具有一个固定的公共IP (203.0.113.99),您已将其allowlisted与三个现有合作伙伴API(支付处理器,短信网关,电子邮件提供商)。
一個產品團隊想要添加一個第四個整合:一個回呼傳遞系統,該系統會將回呼傳遞給全球各地的客戶端。預測的流量量為每分鐘10,000個呼叫,峰值達到30,000個。
為一個逐漸擴大的服務設計網絡邊界
結合
你已經學會了為什麼入口和出口需要不同的工具,實際部署中的髮夾NAT失效強制分離,以及在分離落地後,獨立擴展、安全隔離和故障隔離是如何累積的。
應用所有四個。
一家中型的SaaS公司為他們的用戶提供三個產品子域名(app,api,admin),以及四個出bounds整合(Stripe,Twilio,SendGrid,一個客戶回呼系統)。今天,所有東西都位於一個公共IP的單一代理機器後面。他們已經開始收到髮夾NAT失效的中斷報告,當內部服務嘗試調用api.example.com時。他们想要設計一個持久的解決方案。
這個課程的下一步
下一步
你現在已經看到了分佈式系統中的一個最清晰的分擔重任重構:一個盒子變成了兩個,每個都有明確的角色,系統在此過程中獲得了擴展、安全性和故障隔離的好處。
下一個課程(cs_distsys_failure_modes_and_blast_radius)將故障隔離推理擴展。你將閱讀一個清潔的DNS-SERVFAIL後 mortem,識別出瀰漫的故障模式,並撰寫無罪的行動項目,目標是系統而不是人們。
陪讀課: geometry_of_ingress_egress_separation 將分裂視為一個雙部圖並探討切點、網絡分區以及圖論對於網絡邊界的解釋。
好極了。繼續前進。