AWS Beanstalk: Go WebApp 部署筆記
記錄前陣子將使用 Go 所寫的網路服務 (Restful API server),部署到 AWS Beanstalk 的筆記記錄,讓未來部署除了官方文件外,還有一個參考筆記。
為什麼採用 AWS Beanstalk 佈署
這得先提伺服器部署模式,如下圖所示,最左邊是偏 Infrastructure as a service (IaaS),從最基礎的 VM 開始架設服務,彈性最高,但得自行設定環境 (Environment)。最右邊是偏 Platform as a service (PaaS),部署只關心服務程式,環境設定托給系統服務商處理。或是中間使用容器技術 (Container) 技術來發布服務。關於更多 IaaS 以及 PaaS 的描述,可參考筆記末的 Reference。
原先我們的服務環境也是自己管理設定,租用 EC2,安裝服務執行環境,設定 LoadBalancer,建立 Amazon image 以設定 Autoscaling group……。後來發現我們沒有能力以及人力,去管理日漸龐大的系統。因此開始研究 Beanstalk,只需要關心服務程式碼,剩下的都交給 AWS 協助管理。
建立 Source bundle 部署到 AWS Beanstalk
從 AWS 官方文件中,了解要佈署到 Beanstalk 有幾種方式,根據需求將服務資源包成 Source bundle 後上傳,接著用其 Beanstalk 佈署到環境即可。主要根據這兩條需求而有四種包法:
- 是否為複雜的服務?(需要執行額外的程序,或者需要調整 Beanstalk 環境)
- 使用 Binary 佈署嗎?
可以畫成以下的流程圖來決定:
紀錄其中複雜環境上傳程式碼的打包佈署方式,需要打包 Beanstalk 環境設定檔 (/.ebextensions)、程式碼、程式碼編譯命令 (BuildProc)、以及服務啟動命令 (Profile)。整理打包成 app.zip
source bundle 的資料結構如下:
- .ebextensions/
- applogs.config
- src/
- app/
- main.go
- vendor/
- Procfile
- Buildfile
- build.sh
- .ebextensions 資料夾內放置所有的環境設定檔,Beanstalk 環境會根據其 yaml 設定,建立或是修改指定檔案的內容
- .ebextensions/applogs.config,建立 Log 設定檔,進行 S3 Log rotate,將本機端的 Log 複製到 S3 空間上,要注意的是寫入
os.Stdout
以及os.Stderr
的內容,會分別存放在/var/log/web-1.log
以及/var/log/web-1.error.log
這兩個檔案中files: "/opt/elasticbeanstalk/tasks/publishlogs.d/applogs.conf" : mode: "000755" owner: root group: root content: | /var/log/web-1.log /var/log/web-1.error.log
- src/app/,放置建置網路服務程式的程式碼
- src/app/vendor/,放置所有使用到的相依 package 程式碼,可以以下使用工具進行管理 govendor, dep, godep, or glide
- ProcFile,當環境準備好後的啟動服務命令
web: ./app
- Buildfile,環境準備的建置命令
make: sh build.sh
- build.sh,主要的 Go 程式碼建置指令,
$APP_STAGING_DIR
是佈署服務的暫存位置export GOPATH=$APP_STAGING_DIR go build -o app -v app
當這份 source bundle 上傳到 AWS Beanstalk 環境並執行佈署時,會依序執行步驟:
- .ebextensions
- Buildfile
- Procfile
關於這份 Go 佈署 template 已上傳 Github [twsiyuan/aws-beanstalk-go-deploy-template] 上。
關於網路 Port 監聽選擇
Beanstalk 環境會有設定,應該監聽哪個連接埠 (port) 才能服務正常運作,從環境參數 Environment 取得:
addr := ":" + os.Getenv("PORT")
http.ListenAndServe(addr, handler)
若是開發本機端測試,可以考慮加入從 command-line flag 或是 config file 讀取:
port := flag.String("PORT", "8080", "listening port")
flag.Parsed()
if sysPort := os.Getenv("PORT"); len(sysPort) > 0 {
port = sysPort
} else if cfgPort := fromCfgPort(); len(cfgPort) > 0 { // TODO: fromCfgPort
port = cfgPort
}
addr := ":" + os.Getenv("PORT")
http.ListenAndServe(addr, handler)
關於網路架構以及 Remote Addr
若佈署的是 Web Go application,要特別注意的是 Remote Addr 取得,不再能以下那樣的方式來取得遠端用戶的 IP 位置,這將取得 Nginx 的伺服器在本機端的位置 127.0.0.1 :
func h(w http.ResponseWriter, req *http.Request) {
println(req.RemoteAddr)
}
這是因為改用 AWS Beanstalk 後,Web app 不再直接面對客戶端的連結,網路服務架構被調整如下圖:
Ngnix 會負責轉傳 Load Balancer 來的要求給 WebApp,因此從 headers X-Forwarded-For
拿到其要求的原始來源位置:
req.Header.Get("X-Forwarded-For")
它會是一長串的 IP address,例如以下示意:
cleint-ip-address, load-balancer-address
如果用戶端本身使用 proxy 代理連線,LoadBalancer 也會轉傳 proxy 所帶的 X-Forwarded-For
資訊,那麼 ip address 可能以下,其中 ip-address-3 為最後的代理伺服器位置,ip-address-1 是可能是用戶端的實體位置:
ip-address-1, ip-address-2, ip-address-3, load-balancer-address
Reference
- Pizza as a Service - On Prem, IaaS, PaaS, and SaaS Explained through Pie (Not Pi)
- Wiki: Cloud computing
- AWS Doc: Deploying Go Applications to Elastic Beanstalk Applications
- AWS Doc: Using the AWS Elastic Beanstalk Go Platform
- AWS Doc: HTTP Headers and Classic Load Balancers
沒有留言: