[筆記] AWS ALB 502 Bad Gateway Errors Caused by Short HTTP Keep-Alive Timeouts
我的 AWS ALB 502 Bad Gateway 問題解析之旅
Preface
本文用以記錄筆者前陣子在工作上,因為後端伺服器 HTTP keep-alive timeout 設定過短,所造成的 AWS ALB 502 bad gateway 問題。文章內容包括觀察與分析問題、釐清原因,及提出解決方案。
Context
Tremendous Bad Gateway Errors Observed on AWS ALB
前陣子,我們觀察到公司內部某一個服務所使用的 AWS ALB 不時出現 502 bad gateway (Figure 1)。此外,在其他相依的服務上也的確觀察到了不少失敗的 HTTP 請求。但是,當我們透過 monitor tool (New Relic) 檢查 ALB 所對應的後端伺服器時,卻發現與 ALB 上觀察到的狀態並不吻合 (Figure 2)。儘管在對應的時段裡伺服器的確有零星的 502 發生,但與 ALB 上每小時上千筆的錯誤相比,明顯量級上有所差異。
由於 New Relic Agent 是埋在伺服器的 middleware,所以只能記錄到實際上有接收並處理的請求。因此可以初步研判,大量的 502 並不是伺服器本身處理邏輯所造成的,更有可能是網路連線上的異常。
Troubleshooting
Survey on AWS ALB Documents
工欲善其事,必先利其器。在猜測問題主因可能是網路連線異常後,我們決定先來研究 AWS ALB 的官方文件,看看是否能在上面找到線索。果不其然,在章節 Troubleshoot Your Application Load Balancers 中找到相關的說明。其中有一段原因吸引了我們的注意:
HTTP 502: Bad Gateway
Possible causes:
……
* The target closed the connection with a TCP RST or a TCP FIN while the load balancer had an outstanding request to the target. Check whether the keep-alive duration of the target is shorter than the idle timeout value of the load balancer.
//簡單白話文:HTTP 請求在得到回應前,連線就被伺服器關掉了。快去檢查伺服器端的 keep-alive 是不是設得太短了!
……
The HTTP Connection Management on AWS ALB
這邊我們需要先來介紹 AWS ALB 是如何處理經手的 HTTP 請求 (Figure 3)。根據官方文件上的介紹,當一個 HTTP 請求透過 ALB 轉傳到後方伺服器時,這個請求會被拆成兩個連線: Frontend connection 與 Backend connection。
Frontend connection 的生命週期比較單純。基本上用戶端以什麼協定連上來,ALB 就會乖乖地建立對應的連線(常見的 HTTP/1.1 、 HTTP/2,或是 TLS/SSL 加密都有支援)。當用戶端想結束連線時,Frontend connection 也會自然地關閉且丟棄。
Backend connection 的生命週期則比較特別。為了降低延遲,減少建立連線的次數,Backend connection 的使用模式被設計成 connection pool 的形式。當用戶端連上 ALB 時,ALB 便會在 connection pool 中找有沒有閒置的 Backend connection ,如果有就會直接拿來用; 如果沒有則建立新的連線(通常是非加密的 HTTP/1.1)。為了有效地重複利用連線,官方會建議伺服器要啟用 HTTP keep-alive。
除了基本的生命週期管理外,為了避免客戶端或伺服器設定錯誤,造成一堆 zombie connection,ALB 上還有一個 idle connection timeout 的設計。當連線閒置超過一段時間後, ALB 會發出終止連線的請求,關閉連線。這個 timeout 的預設值為 1 分鐘。
Check Related HTTP Settings in The Backend Servers
再發現ALB 會重複利用 backend connection 且具有 idle connection timeout 的設定後,我們接著回頭檢視後端伺服器的 HTTP 連線設定是否正確。我們的後端伺服器使用的是 Node.js,版本是 12.16.2 TLS。在這個版本當中, HTTP keep-alive 預設是打開的,但是預設值是5秒(!)
The Root Cause
至此,我們可以拼湊出整個完整的故事:由於伺服器的 HTTP keep-alive timeout 設定太短,導致 backend connection 在重複使用的途中不預期地被伺服器端中斷。因為連線被中斷,所以 AWS ALB 只好回傳用戶端 502 bad gateway。
The Proposed Solution
解法其實滿簡單的,就是把伺服器的 HTTP keep-alive timeout 的值設超過 AWS ALB 的 idle connection timeout 。沒錯,就是如此直覺XD 這樣管理 backend connection 生命週期的權力,又回到了 AWS ALB 身上,就可以避免 ALB 拿(即將)爛掉的連線出來用。
const app = require('express')();
const server = require('http').createServer(app);
server.keepAliveTimeout = 65000;
server.listen(......, ()=>{......});
我們在 2020-04-16 06:00 (UTC+0) 左右重新部署了伺服器,一切的 502 bad gateway 都消失了 (Figure 4)。世界又再次回復了和平,感謝工程師們的努力。
Takeaways
- AWS ALB 的 backend connection 會重複使用,建議伺服器啟用 HTTP keep-alive 。
- 伺服器的 HTTP keep-alive timeout 長度需要超過 AWS ALB idle connection timeout,可以避免不預期的 502 bad gateway 發生。
- 使用任何 proxy 或 gateway 時,多花點心思搞懂它們的運作原理。 Connection reuse 的確是用意良善好處多多,但使用不當還是會翻車。