Laravel - 高併發

# 前言

紀錄一下 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
第三方登入 k6 - 使用筆記

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×