【Azure 應用服務】App Service/Azure Function的出站連接過多而引起了SNAT埠耗盡,導致一些新的請求出現超時錯誤(Timeout)
- 2021 年 4 月 24 日
- 筆記
- 【Azure 應用服務】, App Service, Azure Function, SNAT耗盡問題
問題描述
當需要在應用中有大量的出站連接時候,就會涉及到SNAT(源地址網路轉換)耗盡的問題。而通過Azure App Service/Function的默認監控指標圖表中,卻沒有可以直接查看到SNAT是否耗盡的問題(可以間接參考App Service Plan級中Metrics的 Socket Outbound All指標[截圖見文末附錄一],但是由於它是整個Plan下所有App Service的匯總數據,不能直接表明SNAT是否超過128的限制)。
這裡所說的出站連接如:SQL資料庫, Redis快取以及其他的Restful API等等需要從App Service中向外發出的請求。當SNAT耗盡後,會出現以下一種或多種問題:
- App Service的響應速度緩慢。
- 間歇性 5xx 錯誤或「錯誤的網關」錯誤
- 超時錯誤消息
- 無法連接到外部終結點(例如 SQL DB,Redis,及其他API等)
問題分析
因為App Service是部署在雲服務中,所以它也遵循著一個集群中很多實例(VM)通過負載均衡器的前端 IP 建立出站連接,所以出站的埠就成了用於維護不同流的唯一標識符。 因為在網路流量的五元組中
- 目標 IP
- 目標埠
- 源 IP
- 源埠
- 協議
如訪問Redis服務(redistest01.redis.cache.chinacloudapi.cn 6380),目標IP為固定不變為RedisHost,而埠則固定為6380,源IP為當前App Service的出站IP,協議方式為Redis的序列化協議(RESP)。以上1,2,3,5都不可變的情況下,只有4 源埠可以改變,所以這裡就需要使用SNAT。
如App Service中一個示例(Worker Instance)發送TCP協議,介紹SNAT的工作流程:
1)App Service應用發送一個TCP包到外部IP地址,源地址和埠在TCP包中。
2)TCP包從App Service應用的工作實例上發送到SNAT負載均衡,SNAT改變了TCP包中的源地址為負載均衡器的公共IP地址和埠號。然後發送到外面目標IP地址。
Attribute | Value |
---|---|
Protocol | TCP |
Worker instance IP address:port | 10.0.5.60:51014 |
Load balancer IP address:port | 13.76.245.72:12481 |
External endpoint IP address:port | 52.189.232.180:80 |
3)外部服務接收到這個TCP包後,會原路回包,它會使用負載均衡器的公共IP地址和埠後作為目標IP和埠。
4)當負載均衡器收到外部服務的回包後,它將根據第2步中的映射關係,修改TCP包中的目標IP和埠。如此TCP回包就正確的回到了App Service的工作實例上。
負載均衡器的前端 IP 分配的每個公共 IP 都會為其後端池成員分配 64,000 個 SNAT 埠,後端池中大約有400多個實例,所以大約分配到每個實例的SNAT埠為64,000/400 =160(最大), 但是實際上分配的個數為128個。
間歇性連接問題的主要原因是在建立新的出站連接時遇到限制。 可以命中的限制包括:
- TCP 連接數:可以建立的出站連接數有限制。 對出站連接的限制與使用的輔助角色的大小關聯。
- SNAT 埠: Azure 使用源網路地址轉換 (SNAT) 和負載均衡器 (不向客戶公開,) 與公共 IP 地址進行通訊。 最初為 Azure 應用服務中的每個實例預分配了 128 個 SNAT 埠。 SNAT 埠限制會影響與相同地址和埠組合的打開連接。 如果應用與混合的地址/埠組合建立了連接,則不會用盡 SNAT 埠。 重複調用同一個地址/埠組合時,會用盡 SNAT 埠。 釋放某個埠以後,即可根據需要重複使用該埠。 只有在等待 4 分鐘後,Azure 網路負載均衡器才會從關閉的連接回收 SNAT 埠。
當應用程式或功能快速打開新的連接時,它們可能很快就會耗盡預分配的配額(128 個埠)。 然後,應用程式或功能會一直受到阻止,直到通過動態分配額外的 SNAT 埠或者通過重複使用回收的 SNAT 埠提供了新的 SNAT 埠為止。 如果你的應用程式用完了 SNAT 埠,則會出現間歇性的出站連接問題。
解決辦法
避免 SNAT 埠問題意味著需要避免對同一主機和埠反覆創建新連接。 連接池是解決該問題的更顯而易見的方法之一。
如短時間無法修改程式碼,基於App Service, Azure Function的易擴展的特性,可以增加實例個數來及時緩解SNAT的受限問題。當每增加一個實例,SNAT埠即可增加128個。
建立連接池的方式一:HttpClientFactory 建立 HTTP 連接池
儘管 HttpClient 實現了 IDisposable
介面,但它是為重複使用而設計的。 關閉 HttpClient
的實例使套接字在 TIME_WAIT
一小段時間內處於打開狀態。 如果經常使用創建和處置對象的程式碼路徑 HttpClient
,應用可能會耗盡可用的套接字。 ASP.NET Core 2.1 中引入了HttpClientFactory作為此問題的解決方案。 它處理池 HTTP 連接以優化性能和可靠性。
建議:
- 不要 直接創建和釋放
HttpClient
實例。 - 請 使用 HttpClientFactory 來檢索
HttpClient
實例。
示例部分
1)以下程式碼即可復現SNAT快速耗盡的情況(沒有及時釋放Response資源)
public string Index(string url) { var request = HttpWebRequest.Create(url); request.GetResponse(); return "OK"; }
可以修改為:
public string Fin(string url) { var request = HttpWebRequest.Create(url); var response = request.GetResponse(); response.Close(); return "OK"; }
2)下面使用Using來釋放httpclient對象,但是也是用以導致SNAT耗盡的問題。
public async Task<string> Client(string url) { using (var client = new HttpClient()) { await client.GetAsync(url); } return "OK"; }
可以考慮重用HttpClient來緩解SNAT耗盡問題
private static Lazy<HttpClient> _client = new Lazy<HttpClient>(); public async Task<string> ReuseClient(string url) { var client = _client.Value; await client.GetAsync(url); return "OK"; }
附錄一:應用服務計劃中查看全部的出站Socket連接
參考資料
使用 SNAT 進行出站連接(負載均衡器默認埠分配) : //docs.microsoft.com/zh-cn/azure/load-balancer/load-balancer-outbound-connections#default-port-allocation
排查 Azure 應用服務中的間歇性出站連接錯誤://docs.microsoft.com/zh-cn/azure/app-service/troubleshoot-intermittent-outbound-connection-errors#avoiding-the-problem
SNAT with App Service,介紹Azure App Service中SNAT的原理,問題,及如何避免?://www.cnblogs.com/lulight/articles/13543209.html