AWS ELB 常見問題、筆記

前言

當了一陣子鍵盤柯南,回饋一下社群,希望大家上雲之路可以少踩點坑。

閱讀本文章建議先看 H1 標題H2 症狀來快速找到對應內容。

如何選擇 ELB?

建議查看 ELB 比較文件,以下為簡易分法:

  • 如果有 HTTP 需求:ALB 最推,再來是 NLB 與 CLB
  • 如果有 TCP、UDP 需求:NLB 最推,再來是 CLB
  • 如果有流量過濾需求:GWLB

推薦閱讀 ELB 最佳實踐文章

調查 ELB 問題起手式

  • 官方 Troubleshooting 文件
  • 如果從一開始建置就連不上,查 Security Group、Network ACL、Route Table
    • NLB 需要 target 開 SG
    • ALB 需要 ALB 開 SG
    • CLB、ALB、NLB 都不能在 NAT Gateway 後面(除非是 internal ELB)
  • 如果是某段時間突然發生,先把所有 ELB 指標都看一輪
  • 遇到某些錯誤碼:查文件
  • 找特定請求、連線問題:查 log

ELB 需要預熱嗎?

大部分狀況是不用的,因為 ELB 本身會隨著流量自動擴容、縮容,但根據文件如果有「瞬間流量」或是「壓力測試」需求,會建議開案例到 “Account and billing” 請 AWS 協助預熱(不需要買 Support Plan)。

In certain scenarios, such as when flash traffic is expected, or in the case where a load test cannot be configured to gradually increase traffic, we recommend that you contact us  to have your load balancer “pre-warmed”.

為何 Target Timeout 要比 ALB Timeout 長?

症狀

客戶端偶爾看到 HTTP 502,ALB 指標也看到少量 [HTTPCode_ELB_5XX_Count](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html#load-balancer-metrics-alb) ,且隨著請求數上升而上升。

說明

因為 ALB 是做類似 Proxy 的功能,Client 建立 TCP 連線到 ALB,並向 ALB 發送 HTTP 請求,這時 ALB 會根據負載平衡演算法找台 Target 轉發這 HTTP 請求。因此平常 ALB 就會跟 Target 維護 TCP 連線,而不是當 Client 發請求時才跟 Target 建立連線。

                ---> Target
Client ---> ALB ---> Target
								---> Target

以上圖為例,ALB 可能跟後端維持 3 條 TCP 連線,只要是連線就會有 Timeout 設定。

假設 ALB Timeout 設定為 60 秒(預設值),Target 為 NGINX Timeout 設定也為 60 秒:

此時有個 Client 送出 HTTP 請求到 ALB,因此 ALB 根據負載平衡演算法挑了一台 Target 轉發此 HTTP 請求,這時非常不幸的在第 59.9 秒時送出請求,假設線路傳輸時間耗時 0.2 秒,那麼到達 Target 時實際上是 60.1 秒,超過 NGINX Timeout 設定,因此 Target 會 Reset 這條 TCP 連線,ALB 看到 Target Reset,就回傳 502 給客戶。

解法

文件中有明確寫出 Target 的超時設定要比 ALB 設定的還大,假設設定為 62 秒,就不會遇到上述狀況囉!

We also recommend that you configure the idle timeout of your application to be larger than the idle timeout configured for the load balancer

NLB 看到 Client、Target Reset 指標

症狀

如果看到大量 TCP_Client_Reset_CountTCP_Target_Reset_Count,由於 NLB 只做 4 層轉發,一般來說不會主動斷開連線,請調查客戶端、伺服器端是否有發生什麼問題。

另外也請注意是否有在連線超時候傳送封包,TCP 是 350 秒,期間有 TCP keepalive 包即可保持連線不中斷,如果超時候發送封包(不論從 Client 或 Target),就會收到 ELB 的 TCP RST 封包;UDP 連線則是 120 秒。

如果沒有找到問題可以開 case 詢問 support 底層網路是否有狀況。

解法

Client 或 Target 可以使用 TCP keepalive packets 以重設 idle timeout 計時器。

參考文件:

ALB 能不能固定 IP 地址?

基本上不行,但可以透過組合技:

  • NLB 本身就有固定 IP 地址(因為架構關係),可以使用 NLB + ALB 架構
  • Global Accelerator 有 2 組固定 IP 地址,可以後面串 ALB

為何每 60 秒要解析一次 ALB 域名以得到 IP 地址?

症狀

ALB 突然不能連上了,必須重啟 Web Server (NGINX)、軟體才行。

說明

因為架構關係,ALB 可能因為擴、縮容而導致節點替換,請務必每 60 秒解析一次域名以取得最新節點的 IP 地址,避免連不上。

常見問題及解法

如果是以下 NGINX 代理架構:

NGINX ---> ALB ---> Target

請記得不要把域名寫死在 proxy_pass 中,因為 NGINX 只會解析一遍,並不會遵守 TTL 60 秒去解析。

解法:

假設在預設 VPC 中,使用 Default Route 53 Resolver 來解析(IP 地址為 172.31.0.2),當然也可以用第三方如 1.1.1.18.8.8.8 當作 resolver,那麼可以使用以下範例設定檔來達成遵守 TTL 來定時解析域名。

server {
    listen 80;
    ...

    resolver 172.31.0.2 valid=60s;
    resolver_timeout 3s;

    set $proxy_url "example.com";
    location / {
        proxy_pass http://$proxy_url;
        proxy_set_header Host example.com;
        ...
    }
}

推薦閱讀https://shazi.info/當-aws-的-elbalb-被-nginx-代理後的動態-ip-問題/

為什麼延遲升高?

  • 先看 TargetResponseTime,設定 Max、Average、p99。
    • 如果是 Target 問題,那檢查當時 CPU Usage、Disk Usage、Network Usage
    • 可以開 Web Server 的 Performance Monitor log,如 NGINX 可以這麼做
  • 也可能是 ELB 處理時間太久
    • 開 ELB log 看處理時間
  • 延遲升高可能是網路問題導致
    • 自行在 Client 端抓包、ELB 上抓包
    • 若懷疑 AWS 端網路問題,可以開 Support Case 確認

健康檢查失敗

一般來說上不是 timeout 就是 status code 沒有匹配。

  • Timeout 通常是 ELB 無法連上 Target
    • 檢查路由表、Security Group 設定
    • 檢查 EC2 上是不是有兩張網卡,小心去回不同路(非對稱路由)
      • (如果透過 Secondary 網卡收封包,會透過 Primary 網卡回應)
  • Status code ALB 預設只認 HTTP 200

流量不平衡

  • 確定客戶端應用沒有寫死 ELB IP 地址,並且遵守 TTL 每 60 秒解析一次
  • 如果有開檢查 sticky session,請確認客戶端有平均分配
  • 看有沒有開 cross zone
    • CLB 預設
      • 透過 API/CLI 建立的話,關閉
      • 透過 Console 建立的話,開啟
    • ALB 預設開啟
    • NLB 預設關閉

如何限定只能由 CloudFront 連到 ALB

為了安全考量,可以限制只能由 CloudFront 連到 ALB,有幾種做法:

  1. 在 CloudFront 中回源請求加入自定義 Header 欄位,並在 ALB 上驗證這個值
    1. 這個值不能外流,以免被黑客利用
  2. 在 ALB 的 Security Group 套用 CloudFront 的 IP prefix

NGINX 看到 499、ELB 看到 460

症狀

NGINX log 中看到 HTTP 499 代表 Client 傳送了 HTTP 請求,但在 Server 送出 HTTP 回應前客戶端就關閉連線了,如果 NGINX 的 Upstream 是 ELB 的話,ELB 會看到 HTTP 460。

Client ---> NGINX ---> ALB ---> EC2/EKS

解法

排除掉 Client 有問題而提早關閉連線狀況,透過 ALB 指標應該可以看到 TargetResponseTime 偏高。常見原因為:

  1. EC2/EKS 資源不夠,嘗試加大 instance type、Pod 數量
  2. 系統當時有些排程在處理(e.g., 套件更新、系統更新)
  3. 該 HTTP request 所耗資源甚多(e.g., 資料庫寫入)

參考文件:

  1. NGINX 499 定義程式碼
  2. ALB HTTP 460

ALB 看到 TLS 協商失敗指標調查

由於協商成功後才會在 ALB log 中有紀錄,因此建議 Traffic Mirroring 看問題(小技巧:可以先用 VPC flow log 比對 ALB log 看哪些 IP 地址沒出現在 ALB log)。

wscat 使用教學

server:

wscat -l 8000

client:

wscat -c ws://localhost:8000

NLB Target 自連問題

症狀

使用 internal NLB 時,如果有開啟 Client IP Preservation,當 target 對 NLB 發起連線時剛好連到自己,那麼 OS 就不會回應。

Client ---> NLB
^            |
|-<-----<----|

實驗

參數:

  • Client(也就是 Target):172.31.45.13
  • NLB:172.31.43.249

抓包結果:

172.31.45.13.51564 > 172.31.43.249.5201: Flags [S], seq 254401894, win 26883, options [mss 8961,sackOK,TS val 3189050549 ecr 0,nop,wscale 7], length 0
172.31.45.13.51564 > 172.31.45.13.5201: Flags [S], seq 254401894, win 26883, options [mss 8645,sackOK,TS val 3189050549 ecr 0,nop,wscale 7], length 0
172.31.45.13.51564 > 172.31.43.249.5201: Flags [S], seq 254401894, win 26883, options [mss 8961,sackOK,TS val 3189051549 ecr 0,nop,wscale 7], length 0
172.31.45.13.51564 > 172.31.45.13.5201: Flags [S], seq 254401894, win 26883, options [mss 8645,sackOK,TS val 3189051549 ecr 0,nop,wscale 7], length 0
172.31.45.13.51564 > 172.31.43.249.5201: Flags [S], seq 254401894, win 26883, options [mss 8961,sackOK,TS val 3189053565 ecr 0,nop,wscale 7], length 0
172.31.45.13.51564 > 172.31.45.13.5201: Flags [S], seq 254401894, win 26883, options [mss 8645,sackOK,TS val 3189053565 ecr 0,nop,wscale 7], length 0

參考文件

NLB 鑽石路由問題

症狀

當 NLB 啟用 Client IP Preservation 功能,有低機率會發生連不上問題,且一直存在。

說明

使用相同 Client IP 地址、port 對兩個 NLB 節點(可以是不同 NLB 資源)發送請求,如果 Target 都是同一台 EC2 那麼便會發生問題,因為兩條 TCP 連線有相同的 5-tuple。因為長得有點像鑽石(越多條越像,請自行想像…),因此 AWS 稱為鑽石路由問題。

                       ---> NLB (2.2.2.2)
                      /                  \
 Client (1.1.1.1:5555)                    ----> Target EC2 instance
                      \                  /
                       ---> NLB (3.3.3.3)

解法

由於 Client IP Preservation 特性使然,關掉 Client IP Preservation 功能即可。如有得知客戶端 IP 地址需求,可以使用 Proxy Protocol v2

參考文件:

同時連 NLB 和 Target 問題

症狀

Client 同時對 NLB、Target 發起連線,其中一條會不通。

            --->--------------------
            |                      |
 Client (1.1.1.1:9999) <-----------
            |                      |
            ---> NLB (2.2.2.2) -----

說明

因為目標不同,因此可能重用 source port 導致鑽石路由問題,請看上一條說明。

參考文件:

實驗

(以下公網 IP 地址經過變造,如有雷同純屬巧合)

因為要使用同個 IP 地址、port 對目標進行連線,因此需要額外啟用 SO_REUSEPORT 以重用 port,想了解 REUSEPORT 與 REUSEADDR 可以參考這篇

a.py(對自己連線)

import socket
import sys
import time

print("source port: ", end='')
s_port = int(input().strip())
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
server_addr = ("35.75.51.172", 5201)
sock.bind(('172.31.45.13', s_port))
sock.connect(server_addr)
print("connected")
sock.send(b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
print(sock.recv(100).decode("utf8"))
time.sleep(120)

b.py(對 NLB 連線)

import socket
import sys
import time

print("source port: ", end='')
s_port = int(input().strip())
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
server_addr = ("18.176.35.12", 5201)
sock.bind(('172.31.45.13', s_port))
sock.connect(server_addr)
print("connected")
sock.send(b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
print(sock.recv(100).decode("utf8"))
time.sleep(120)

先後執行 a, b 兩支腳本後,其中一條連線沒辦法建立,一直在 SYN_SENT 狀態:

$ sudo netstat -ntp | grep 9999
tcp        0      0 172.31.45.13:5201       35.75.51.172:9999       ESTABLISHED 7185/nginx: worker
tcp      149      0 172.31.45.13:9999       35.75.51.172:5201       ESTABLISHED 19260/python3
tcp        0      1 172.31.45.13:9999       18.176.35.12:5201       SYN_SENT    19277/python3

觀察抓包,可以看到 01~14 行是「Target 對自己的公網 IP 地址」發送請求,可以正常收到回應。

15~26 是「Target 對 NLB 發送請求」,因為有相同的來源 IP 地址、port(前一條連線尚未關閉),因此 Target 一直回應 SACK。

01.  IP 172.31.45.13.9999 > 35.75.51.172.5201: Flags [S], seq
02.  IP 35.75.51.172.9999 > 172.31.45.13.5201: Flags [S], seq
03.  IP 172.31.45.13.5201 > 35.75.51.172.9999: Flags [S.], seq
04.  IP 35.75.51.172.5201 > 172.31.45.13.9999: Flags [S.], seq
05.  IP 172.31.45.13.9999 > 35.75.51.172.5201: Flags [.], ack
06.  IP 172.31.45.13.9999 > 35.75.51.172.5201: Flags [P.], seq
07.  IP 35.75.51.172.9999 > 172.31.45.13.5201: Flags [.], ack
08.  IP 35.75.51.172.9999 > 172.31.45.13.5201: Flags [P.], seq
09.  IP 172.31.45.13.5201 > 35.75.51.172.9999: Flags [.], ack
10.  IP 172.31.45.13.5201 > 35.75.51.172.9999: Flags [P.], seq
11.  IP 35.75.51.172.5201 > 172.31.45.13.9999: Flags [.], ack
12.  IP 35.75.51.172.5201 > 172.31.45.13.9999: Flags [P.], seq
13.  IP 172.31.45.13.9999 > 35.75.51.172.5201: Flags [.], ack
14.  IP 35.75.51.172.9999 > 172.31.45.13.5201: Flags [.], ack
15.  IP 172.31.45.13.9999 > 18.176.35.12.5201: Flags [S], seq
16.  IP 35.75.51.172.9999 > 172.31.45.13.5201: Flags [S], seq
17.  IP 172.31.45.13.5201 > 35.75.51.172.9999: Flags [.], ack
18.  IP 35.75.51.172.5201 > 172.31.45.13.9999: Flags [.], ack
19.  IP 172.31.45.13.9999 > 18.176.35.12.5201: Flags [S], seq
20.  IP 35.75.51.172.9999 > 172.31.45.13.5201: Flags [S], seq
21.  IP 172.31.45.13.5201 > 35.75.51.172.9999: Flags [.], ack
22.  IP 35.75.51.172.5201 > 172.31.45.13.9999: Flags [.], ack
23.  IP 172.31.45.13.9999 > 18.176.35.12.5201: Flags [S], seq
24.  IP 35.75.51.172.9999 > 172.31.45.13.5201: Flags [S], seq
25.  IP 172.31.45.13.5201 > 35.75.51.172.9999: Flags [.], ack
26.  IP 35.75.51.172.5201 > 172.31.45.13.9999: Flags [.], ack

如何在 ELB 上抓包

參考 Traffic Mirroring

ALB 支援 WebSocket 嗎?

ALB 支援,可以用 wscat 測試。

ALB 路由演算法跟 Sticky Session 哪個優先權比較高?

根據文件,sticky session 高於路由演算法(LOR、RR)。

If you enable sticky sessions, the routing algorithm of the target group is overridden after the initial target selection.

可以連上負載平衡,但是無法登入

  • 可能是網頁是有狀態的(如 CSRF token、cookie),啟用 sticky session 即可
  • 但考慮到可擴展性,最佳實踐會是將 Web Server 設計成無狀態的,狀態管理可以靠 RDS、DocumentDB、ElastiCache 等

多台 ELB 同時延遲升高,並且看到 HTTP 460

原因:

  • 如果 Target 彼此有相依關係,那麼可能因為其中一個依賴出問題導致
  • AWS 內部問題

為何 CloudFront 連 ALB 不走 HTTP/2?

雖然 CloudFront 對 Client 端支援 HTTP/2,但回源仍走 HTTP/1.1

Curl NLB TLS 1.3 連接失敗

有可能是部分 curl 版本不支援一些 cipher suite,建議使用最新版測試,或直接用瀏覽器測試。


文件重點筆記

在嗑文件時有記錄些重點,提供給大家參考。


comments powered by Disqus