關於 web service, unity, blogger 等軟體工程筆記

AWS Beanstalk: HTTP 502 Upstream sent too big header while reading response header from upstream

Edit icon 沒有留言
Amazon web services

最近檢查即將上線的服務,發現主要服務經常發生 HTTP 500 的錯誤,從 logs 中確認是其中某個 REST 微服務回應 HTTP 502 Bad Gateway 所導致的,因而從中翻翻閱該微服務的全部 logs,並找到出錯原因以及解決方法。

尋找在 Beanstalk 服務回傳 HTTP 502 的錯誤原因

一開始是以為是該微服務的後端應用伺服器 (back-end application) 壞掉,其服務程式可能寫得不好,存在會造成 crash 無法重啟的嚴重錯誤,使得 load balancer 因為沒有適用後端應用伺服器,只能回應 HTTP 502。

但根據錯誤發生時間以及該 ELB (Elastic Load Balancing) 的監看數據 (‘HTTPCode_Backend_5XX’ in Cloudwatch metrics) 來看,ELB 沒有丟出任何的 5XX 錯誤,且監看數據顯示,後端應用伺服器常駐健康狀態 (keep health)。

那麼根據 Beanstalk 的架構,不是後端應用伺服器丟出,不然就是其反向代理 (reverse proxy) - nginx 丟出的 HTTP 502 回應。

Beanstalk 網路架構示意圖

Beanstalk 網路架構示意圖

應用程式自己開發,可以肯定絕對不會丟出 HTTP 502 的回應,那麼勢必是 nginx 所發出的回應。透過 Beanstalk Web 介面拿到所有實體的 logs,在 /var/log/nginx/error.log 找到錯誤訊息:

2018/01/07 16:04:42 [error] 5305#0: *174803 upstream sent too big header while reading response header from upstream, client: 172.31.26.140, server: , request: “POST /v3/next HTTP/1.1”, upstream: “http://127.0.0.1:5000/v3/next”;, host: “exampleserver-env.ap-northeast-2.elasticbeanstalk.com”

Upstream sent too big header while reading response header from upstream 錯誤的處理方式

有錯誤訊息很容易地便能在 StackOverflow 上找到解法 (see Reference link),因為受限於 nginx 的設定,無法處理後端應用伺服器回傳過大的 HTTP headers,進而回應 HTTP 502。

由於 nginx 是作為 reverse-proxy 在使用,有兩種修改 nginx 設定方法,一種是調整 proxy buffer 上限:

http {
   proxy_buffer_size 256k;
   proxy_buffers 4 512k;
   proxy_busy_buffers_size 512k;
}

另一種是關閉 proxy buffer,將後端應用伺服器的回應,直接同步丟給用用戶端:

http {
   proxy_buffering off;
}

更細節的設定說明請參考 nginx 官方說明頁

Beanstalk nginx 設定

由於該微服務是採用 source bundle 部屬在 Beanstalk 上,所以參考 AWS 文件,以及 AWS Beanstalk nginx 的預設設定檔,其設定步驟:

  • 在 source 根目錄下,加入檔案 /.ebextensions/nginx/conf.d/00-no-proxy-buffering.conf
  • 其檔案內容為上節提到的設定
    proxy_buffer_size 512k;
    proxy_buffers 4 1m;
    proxy_busy_buffers_size 1m;
    
  • Line-endings 調整為 UNIX (如果是在 Windows 環境編輯的話),且編碼為 UTF-8 without BOM
  • 重新打包 source bundle
  • 上傳並部屬該版本

可能會有疑問,為什麼 00-no-proxy-buffering.conf 內容不需要加入 http { ... } 區塊,這是因為 Beanstalk 啟動 nginx 的設定檔內容關係,可以留意到是 Line18 include conf.d/*.conf;,由於該宣告已包含在 http { ... } 區塊內:

# Configuration: 64bit Amazon Linux 2017.09 v2.7.1 running Go 1.9
# Elastic Beanstalk Nginx Configuration File
user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    16027;
events {
   worker_connections  1024;
}
http {
   include       /etc/nginx/mime.types;
   default_type  application/octet-stream;
   log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';
   
   include       conf.d/*.conf;
   
   map $http_upgrade $connection_upgrade {
      default     "upgrade";
   }
   
   server {
      listen        80 default_server;
      access_log    /var/log/nginx/access.log main;
      
      client_header_timeout 60;
      client_body_timeout   60;
      keepalive_timeout     60;
      gzip                  off;
      gzip_comp_level       4;
      gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
      
      # Include the Elastic Beanstalk generated locations
      include conf.d/elasticbeanstalk/*.conf;
   }
}
筆記:實務上將 00-no-proxy-buffering.conf 設定為 proxy_buffering off;,還是會發生 Upstream sent too big header 的錯誤…不知道是無法關閉 proxy buffering 抑或是遇到奇妙的 bug?

Reference

AWS Beanstalk 系列文章

沒有留言: