# 前言
紀錄一下 5000 RPS 併發的專案架構
# 部署
全套架構
# AMI 製作
啟動一台 instance, 安裝必要環境, 視需求可參考 Laravel 部署
# 前端專案
部署在 AWS S3 上, 並使用 CloudFront 來 cache 靜態檔案
整個架構大概是 CI/CD 將專案部署到 s3
, CloudFront 建立從 s3 取得資源, 並 cache
, 將 domain map 到 CloudFront 分佈的 domain
# s3 設定
建立一個 bucket, 並從屬性設定靜態網站託管
取消封鎖公開存取
儲存貯體政策定義, 讓 bucket 可被存取, 但僅限特定的 referer
{
"Version": "2012-10-17",
"Id": "http referer policy example",
"Statement": [
{
"Sid": "Allow get requests originating from www.example.com and example.com.",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::貯體名稱/*",
"Condition": {
"StringLike": {
"aws:Referer": "設定在 cloud front 的 referer value"
}
}
}
]
}cors 設定, 若有使用 CloudFront, 可直接設定在 CloudFront。 若不特別定義, s3 針對 option request 的 response, 不會帶著
Access-Control-Allow-origin
相關的 header, 直接的結果是, 當瀏覽器使用非簡單請求時, 就會被 cors block[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
# CloudFront
- 來源網域輸入 s3 static site url
- 名稱隨便
- 新增自訂標頭, 這邊新增一個 key 為
Referer
, value 自訂的 header, 主要是讓 s3 識別用, 這樣可以阻擋一部分非 CloudFront 對 s3 所發出的請求
- 回應標頭政策, 可加入 CORS, 不然就是要在 s3 處做設定
- 針對 error 404, 一律導向 index.html
- 若專案有更新, 都需 invalidate cache, 手動的話可從 console 操作, CI/CD 的話需呼叫 AWS API
- 若要使用 HTTPS, 可使用 ACM 來取得憑證, 用於 CloudFront 的憑證是有限制區域的, 僅限於 us-east-1 的 ACM
- 使用 ACM, 替我們擁有的 domain 取得憑證, 需要在 dns 管理介面 (看 domain 在哪買的) 輸入 ACM 產生的 key / value 來成功驗證, 驗證成功後即取得憑證
- 建立分佈後, 在 dns 管理介面建立 cname, 將特定 domain 導向此分佈產生的 domain, 注意憑證須事先設定好, 否則 HTTPS 無法導向
# 後端
# DB
- 使用 console 建立 DB
- 使用生產環境
- SSD 大小關係到 IOPS, 最高級的可使用佈建式 EBS, 若是一般的 SSD, 約
1 GB = 3 IOPS
, 所以 SSD 越大, IOPS 越高, 可視實際用量調整 - 備份時間設定在半夜
- 是否備庫取決於專案形式, 主要就是預算以及需求
- 可自訂 security group, 需開通 port 3306 可傳入, 否則會連不上資料庫
- 可自訂 parameter group, 這是修改 MySQL 參數的唯一方法
- 可自訂登入預設使用者 & password, 以供 APP 存取
- 為安全性, 可設為非公開, 再透過 AWS 機器連進去
- 記得匯出日誌
# DB proxy
- 使用 proxy 的目的, 是為了隊列 connection, 因為 RDS 的 max_connection 是有限的, 且依據每一種規格的 max_connection 會有所不同, 是根據
memory / 某個數
來定義的, 可到 default parameter group 去看 - 代理的運作原理, 是代替 DB 在 DB 之前 proxy, 發給 DB 定義的 connection 數量, 並把超出的隊列
- 建立代理前, 需要先使用 secret manager 來建立新的 secret, secret 的內容為存取 DB 的 account & password
- 可參考這篇文件 來建立 proxy
- 有一點要注意, 由於是 client 連到 proxy, 在連到 DB, 所以 proxy 的 security group inbound 會是允許 EC2 來源的 3306, 而 DB 的 security group inbound 會是允許 proxy 來源的 3306, 所以這邊會有兩個 security group, 一個是 proxy, 一個是 DB, 文件裡沒寫到, 照文件的 security group 配置會失敗
# Redis
- 使用 Elasticache console 建立
- 可定義 security group, 需允許 6379 port, 否則會連不上
- 若資料較不敏感, 不需使用加密, 不管是靜態加密或傳輸加密, 畢竟加密就會對性能造成影響
- 若使用傳輸加密, 可自訂 auth 字串, 在 Laravel 的 .env 會像是這樣
REDIS_HOST=tls://redis 終端節點
REDIS_PASSWORD=auth字串, 若無傳輸加密可留空
# EC2
- 基本上就是開一台乾淨的機器, 將專案會需要用到的環境安裝上去, 然後建立 AMI
# AMI
- 相較於啟動範本, AMI 像是硬碟中的資料, 跟硬體無關, 像是機器規格, 安全群組, 硬碟空間大小
# 啟動範本
- 相較於 AMI, 啟動範本可以指定 AMI, 然後定義其他資訊, 像是機器規格, 安全群組, 硬碟空間大小
- 用於 auto scalling 啟動機器的依據, 需先建立好 AMI
- 可選auto scalling 指引, 會提示哪些選項須留空, 哪邊為必填
- 如果想從現存的範本複製一份, 可指定來源範本
- 需指定是先建立好的 AMI
- 最重要的當屬
使用者資料
, 機器會再啟動後執行使用者資料
當中的 script, 目的是讓你的專案可以在機器啟動後達到 ready to service 的狀態 - 可為範本定義一個 security group
# security group
- AWS 幾乎每種服務都可為其定義 security group
- default security group 無法刪除
- 若要限制特定來源的 port, 比如說, 我只允許來自於 LB 的 80 port 的 request, 那麼除了定義 port 之外, 可將來源定義成該 LB 的 security gruop
# target group
- 可將複數的 instance 註冊到同一個 target group, 亦可稱為 instance group
- 可接收來自於 LB 的 request
# LB
- 將來自於 Internet 的 request 導向後方的 target group
- 可設定 LB 將特定條件的 source 導向特定的 target group
- LB 與 target group 的 protocol 必須一致, 即兩者要嘛都 HTTP, 要嘛都 TCP, 或其他相同的 protocol
- 當使用 HTTPS 時, 憑證可使用 ACM 來取得, 同 CloudFront 的 ACM 設定, 但 LB 的 ACM 不局限於 us-east-1 地區
- 在 DNS 管理介面, 將特定的 domain name cname 到 LB 產生的 domain name, 若 LB 支援 HTTPS 且 request 為 HTTPS, 則 LB 上設定的憑證的 domain name, 需與發出 HTTPS request 的 domain name 相符
- 可從
listener => rule
將特定的來源導向不同的 target group, 可定義多個 target group 以及多種 rule - 可設定 log, 並將其匯出到 s3, s3 bucket 可不必事先建立, 設定 log 匯出時選擇自動建立 s3 bucket 即可
# auto scalling policy
- 當我們已經定義好啟動範本, 就可以定義 auto scalling policy 來自動根據情境 scale instance, 並且使用啟動範本來啟動 instance
- 可根據機器的使用率來決定是否 scale, 也可單純手動控制所需數量
- 可在建立時, 指定要套用的 LB, 選擇該 LB 下的 target group
- 當健康檢查未通過時, auto scaling policy 會自動關掉 unhealthy instance 並且啟動新的, 可設定新的 instance 啟動後, 過多久時間開始做健康檢查, 以避免啟動耗時較久的啟動範本, 還沒啟動好就被關掉了
# CloudWatch
在機器上安裝 cloud watch agent, 可以非同步的將指定的 log 檔定時輸出到 AWS CloudWatch, 只有一台機器時用不到, 但若 LB 架構, 可能會有多台機器的話就必要
使用 cloud watch agent, 而不是直接使用 cloud watch sdk 的原因是, 使用 sdk 由於是在程式端進行 log 串流, 會影響 response time, 若是跑 qeueue 可以解決這個問題, 但必須要額外設定 worker 去跑, 所以倒不如直接使用 agent 跑 backgroud process 來的單純
首先要先安裝 cloud watch agent, 參考 文件
config 可以自動生成預設的, 也可以手動增加, 為了後續可以即時地透過 Git 來對 config 做版本控制並且更新, 我是採手動的, 可參考 文件
example 大概像是這樣, 可安裝後, 輸入 command
amazon-cloudwatch-agent-ctl
獲取各種參數以及用法, 像是$cloudAgentCtl -a fetch-config -m ec2 -c file:$cloudWatchAgentConfigFile -s
, 這樣就是讀取指定位置的 config, 並且重啟, 類行為 ec2{
"agent": {
"logfile": "/var/log/cloud-watch-agent/cloud-watch-agent.log"
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/www/fun-marketing-backend/storage/logs/laravel-*.log",
"log_group_name": "fun-marketing-pro",
"log_stream_name": "backend-api-default",
"timezone": "UTC",
"multi_line_start_pattern": "^\\[\\d{4}\\-\\d{2}\\-\\d{2} \\d{2}:\\d{2}:\\d{2}\\].*$"
},
{
"file_path": "/var/www/fun-marketing-backend/storage/logs/grabRedEnvelope-*.log",
"log_group_name": "fun-marketing-pro",
"log_stream_name": "backend-api-grab-red-envelope",
"timezone": "UTC",
"multi_line_start_pattern": "^\\[\\d{4}\\-\\d{2}\\-\\d{2} \\d{2}:\\d{2}:\\d{2}\\].*$"
}
]
}
},
"log_stream_name": "default",
"force_flush_interval": 15
}
}細節設定檔部分, 可參考你自己寫的 fun-marketing 專案, 大概流程是
push 到 gitlab
, 然後觸發 git runner, 呼叫 codedeploy api
, 然後codedeploy 將 cloud agent config 部署到機器上
, 最後重啟 cloud agent
# CodeDeploy
- 因為使用 AWS 全套服務, CodeDeploy 在一定用量內的話, 基本上可以說是免費的
- 另外就是, 因為專案是 LB 架構, 需要同時部署到多台機器上, 是以 target group 為單位來部署的, 因此使用 AWS CodeDeploy 會相對單純很多
- 首先, 先從 console 去建立一個應用程式
- 這邊看是使用哪種平台, 這個專案是使用 EC2 內部部署的, 簡單來說就是部署對象是 EC2 機器
- 需建立一個 service role 讓 code deploy process 使用, 可以直接建立一個 service role, 然後綁 AWS CodeDeploy 預設的 role 就可
- 可以選擇
Auto scalling 群組
,EC2 執行個體
, 以及現場部署執行個體
來作為部署目標, 可多選而非則一 - 現場部署的官方解釋是
An on-premises instance is any physical device that is not an Amazon EC2 instance that can run the CodeDeploy agent and connect to public AWS service endpoints.
, 這是他跟一般 EC2 部署的差異 - Auto scalling 群組會出現你已經建立的群組給你選
- EC2 執行個體, 可以指定 tag
- 現場部署執行個體也是指定 tag, 但這個 tag 要自己到機器上去下指令定義, 然後才能獲取 tag
- 部署類型可使用就地或藍綠, 藍綠部署主要是利用 LB 多台機器的機制, 新的開起來後再馬上切過去, 然後再關掉舊的, 可以某層面上做到 zero downtime (在不談到 DB 的情況下), 就地部署則是不管三七二十一直接給你部署下去, fun-marketing 專案我在部署 script 有使用特別的方法, 也是會先把新的專案部署好, 再切換過去, 所以在 code deploy 這邊就使用就地部署
- 承上, 由於已經做到 zero downtime, 所以直接就使用 AllAtOnce 組態
- 可以讓負載平衡器在部署時, 阻擋流量, 等到部署完畢再放行流量, 由於在機器上已做到 zero downtime, 所以這邊就沒特別阻擋, 只是說應該上版時, 要選在人少的時候上版, 總不可能在尖峰時段上版吧 QQ
# CodeDeploy Notification
- 在每次部署時, 發送通知到 Slack channel
- 先到 AWS Chatbot config new client, 具體可參考這篇文件, 文件中的 guardrail 可以先隨便選一個, 等到建立完成後再編輯, 因為 guardrail 主要是界定一個權限範圍, 會需要輸入一個 policy, 而照文件上, 會使用 policy template 來建立, 所以在成功建立 chatbot 的同時, 一個預設 notification only 的 policy 也會被建立, 可被用在 guardrail
- 建立 chatbot 時, SNS topic 先不用定義, 在我們建立通知規則時, 將 chatbot 作為 notification target 時, AWS 會自動建立對應的 SNS topic
- 建立完 chatbot 之後, 參考這篇文件 建立 notification rule, 把上面建立的 chatbot 作為目標, 這樣 SNS topic 就會自動被建立
- 最後, 到 chatbot 處 sens test message 看看 channel 是否能收到 test message
留言