[廚餘回收] Cloudflare 不時吐 502(Bad Gateway),追查發現是 Nginx 發生了「99: Cannot assign requested address」的錯誤。

前幾天,公司的網站在尖鋒時刻,不時會從 Cloudflare 吐出 502(Bad Gateway),查看 Web 伺服器、DB 伺服器的 CPU、記憶體全都沒有過載的情況,把調查對象轉往 Nginx,從錯誤日誌當中看到了下面這個錯誤訊息:

99: Cannot assign requested address

TIME_WAIT

這個訊息是 Linux 發出的,原因是「Port 被用光了」,這個問題是這樣的,HTTP Request 的 TCP Connection 連線時間都很短,連線斷線之後 Port 不會馬上被釋放,預設會進入 TIME_WAIT 狀態 60 秒,60 秒之後才會釋放出來給其他連線使用,這個我們可以透過下面這串指令,去計算目前有多少 Port 是 TIME_WAIT 狀態。

sudo netstat -apn | grep TIME_WAIT | wc -l

為什麼 Port 會被用光呢?

眾所皆知,單一網卡可用的 Port 只有 65535 個,但是實際上在 Linux,可用的 Port 還受到 net.ipv4.ip_local_port_rangenet.ipv4.ip_local_reserved_ports 兩個系統參數的影響,我們可以透過下面的指令查看這兩個系統參數的值。

sudo sysctl net.ipv4.ip_local_port_range
sudo sysctl net.ipv4.ip_local_reserved_ports

我們可以看到可用 Port 的範圍是 32768 ~ 60999,只有 28232 個,等同於說,在一個來源 IP、一個目的 IP 的情況下,單一台機器只夠同時 470 個連線 60 秒,連線數只要再多一點,就炸了。

TCP 的連線數是按「來源 IP + 來源 Port + 目的 IP + 目的 Port」算一個,如果伺服器是開放給使用者直接連線的話,Port 是非常夠用的,但是我們公司是把 Nginx 拿來當 Reverse Proxy,在流量達到一個值的時候,Port 就不夠用了。

如何修改?

解決辦法有五個,這五個辦法不衝突,可以合併採用,

  • 修改 net.ipv4.ip_local_port_range 系統參數,增加 Port 的可用範圍。
  • 增加 Web 服務的 Listen Port,比如:81、82、83、…。
  • 增加 Web 服務的 Listen IP
  • 增加 Reverse Proxy 或 Loading Balancer 的 IP
  • 修改 net.ipv4.tcp_tw_reuse 系統參數為 1

網路上有很多文章教我們也要修改 net.ipv4.tcp_tw_recycle 這個系統參數為 1,如果我們的網路環境是採用 NAT 技術,那麼請保持這個系統參數為 0,不要去動它,原因的話,下面有一篇文章有提到。

這裡有一篇針對 TIME_WAIT 狀態更深入探討的文章 - Coping with the TCP TIME-WAIT state on busy Linux servers簡中譯文),值得一讀,讀完之後就可以理解為什麼上述的五個辦法能解決問題。

最終,我採用的方法是修改 net.ipv4.ip_local_port_range 及 net.ipv4.tcp_tw_reuse 這兩個系統參數,其他的我暫時不去動它,我們打開 /etc/sysctl.conf 檔案,加入下面這段設定:

# Solve TIME_WAIT issue
net.ipv4.ip_local_port_range = 5000    65000
net.ipv4.tcp_tw_reuse = 1
修改 net.ipv4.ip_local_port_range 及 net.ipv4.tcp_tw_reuse 系統參數之前,請確保 net.ipv4.tcp_timestamps 這個系統參數的值為 1。

Port 的可用範圍,我根據常規的 TCP/IP 埠號列表,給它一個相對安全的範圍 5000 ~ 65000,設定檔修改完畢後,執行下面這個指令可以立即生效:

sudo sysctl -p

最後,檢查一下我們的設定值有沒有生效?

參考資料

C# 指南ASP.NET 教學ASP.NET MVC 指引
Azure SQL Database 教學SQL Server 教學Xamarin.Forms 教學