【Azure 應用服務】App Service/Azure Function的出站連線過多而引起了SNAT埠耗盡,導致一些新的請求出現超時錯誤(Timeout)

路邊兩盞燈發表於2021-04-24

問題描述

當需要在應用中有大量的出站連線時候,就會涉及到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 建立出站連線,所以出站的埠就成了用於維護不同流的唯一識別符號。 因為在網路流量的五元組中

  1. 目標 IP
  2. 目標埠
  3. 源 IP
  4. 源埠
  5. 協議

如訪問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地址。

AttributeValue
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的工作例項上。

【Azure 應用服務】App Service/Azure Function的出站連線過多而引起了SNAT埠耗盡,導致一些新的請求出現超時錯誤(Timeout)

 

負載均衡器的前端 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連線

【Azure 應用服務】App Service/Azure Function的出站連線過多而引起了SNAT埠耗盡,導致一些新的請求出現超時錯誤(Timeout)

 

 

 

參考資料

使用 SNAT 進行出站連線(負載均衡器預設埠分配)https://docs.microsoft.com/zh-cn/azure/load-balancer/load-balancer-outbound-connections#default-port-allocation

排查 Azure 應用服務中的間歇性出站連線錯誤:https://docs.microsoft.com/zh-cn/azure/app-service/troubleshoot-intermittent-outbound-connection-errors#avoiding-the-problem

SNAT with App Service,介紹Azure App Service中SNAT的原理,問題,及如何避免?:https://www.cnblogs.com/lulight/articles/13543209.html

相關文章