公司的專案發展了將近十年,累積了超多排程,全部專案加起來可能超過五百個,當發生觸發異常停止運作的後續處置代價都極大,也會發生工作執行異常後續不容易追蹤的問題,所以每一段時間就會思考有什麼更理想的解決方案
然後都蠻難找到一個終極的解法跟架構,困擾了也算是蠻長的一段時間,近期又有一次改版,想說來記錄一下這個過程跟心得
需求
專案一開始建立,多少會遇到需要重複觸發排程的工作,而在電商平台的專案,有許多跟金流查帳、商品快取更新,營運工作相關的排程,隨著公司營運變化之下,排程工作只會持續增加,累積下來針對以往的排程工作是否正確執行的監控會變得越來越困難,尤其是負責開發的工程師轉手以後,後續的工程師也不見得理解當初的邏輯跟目的,如何定義該工作是否算正確地完成
除了開發跟維護的問題以外,今天想比較聚焦在排程架構的設計上,因為工作越來越多就不太可能只用一台機器來執行排程,那如果要用數台機器來執行,那勢必就會遇到如何分配工作跟管理資源的問題,每個排程的時間使用的 CPU 資源,記憶體都不一致的情況下,如何確保工作間不會有衝突甚至造成一個異常就摧毀其他無關的工作,如果出現異常如何快速的發現並追朔 log 查詢錯誤的原因
開天闢地
Day 1 所有上述的問題,其實都不重要,不太需要為了還不存在問題煩惱,只要開一台 Production 的機器,甚至跟 Web server 共用,直接編輯 crontab,就可以開始部署排程的工作了
在這個階段你還是會因為遇到一些排程異常的狀況需要追蹤,最基本就是把每次排程執行結果都開個資料夾寫 log 裡例如
* * * * php cronjob.php abc >> /var/log/cron/$job/$date.log
就能蠻簡單有效的在初四以後回來找問題了
初出茅廬
隨著業績的發展,你的排程工作量開始越來越多,至少會有一台機器專職在執行排程工作,但是這台機器的資源使用一直處忙碌的狀態,造成一些工作的執行時間互相影響,開始要規劃高運算的工作不能同時間執行避免資源衝突,你可以一直升級機器來解決,但是到後來已經有一台機器 8 cores 32GB (不是什麼特殊標準)專職在執行排程工作
你或許就會開始考慮把工作切割到不同機器來跑,雖然開始透過分配工作來解決問題,但是你會開始煩惱如何配置會比較穩定,且開始需要有更聰明的管理方式來管理這些工作,否則排程故障,你第一個要煩惱的問題是這個排程是放在哪台機器上了(跟組織規模化的問題有異曲同工之妙),然後你的 log 也是因此分散到了很多台機器之上
這個階段第一個痛點是除錯時的 log 尋找變得麻煩,第二個痛點是配置排程工作變成需要有管理的方式,如果沒有任何工具,就會倚賴特定工程師的經驗跟知識來解決這個問題
在這個階段嘗試過比較理想的解法,是透過 Cloudwatch agent 把 log 都統一收整到 Cloudwatch logs 上面,比較方便管理跟搜尋,甚至還能 query 跟統計每個工作的執行時間,排程工作管理有兩個階段,一個階段是由於要管理分散多台機器的 crontab 有點困難,也有點難維護版本變動,所以把 crontab 整合成一個可管理的檔案 json or yaml 放到專案中,透過 CI/CD 流程把異動的排程工作部署到預先設定好的機器上
天崩地裂
後續工作量持續增長下,管理這幾台機器跟配置的工作已經困難到無法用人腦來理解的時候,卡了很久比較像是需要一個 workers or cluster 架構來解決排程執行問題,但是管理上希望不要有太複雜的維護代價,且在社群上遍尋不著適合的參考
也剛好這個階段,已經引入容器化技術在管理 Web server 的 auto scaling,而使用的架構是 AWS ECS,所以延續這個架構會在機器管理上比較容易,而剛好那段時間 ECS 碰巧有推出 Scheduled tasks,可以透過 cloudwatch Events 來 Trigger task,蠻符合我們需求的,所以我們就把工作都遷移上去了
遷移完可以達成的效果,大概是每一個工作排程觸發都是 AWS 代管,不會因為特定觸發工作的機器故障,影響到所有排程,但是資源的配置因為還是放在 ec2 上跑 container,還是可能因為某台 ec2 故障造成某部分工作的異常,但是資源管理可以倚賴 ECS cluster ,甚至也可以做到 Auto Scaling 避免因為特定排程工作資源不足引發雪崩,可以蠻動態的調整機器的資源,如果沒有預算限制還可以直上 fargate 完全避免資源衝突造成的危害。
移天換日
有一段期間為了幫助大家更容易地檢索跟分析 log ,導入了 ELK 的架構,把 Container 裡面多配一個 Filebeat 來收 log 打到 Elasticsearch,可以在 Kibana 上面直接用關鍵字搜尋觀察特定工作的異常或錯誤,也能透過 parse 過的 cronjob 執行數據作成視覺化報表來統計每個工作的執行時間、資源用量等狀態,可以快速看出哪些工作有效能異常值得改進
其實後來 CloudWatch 的功能一直進化後覺得 ELK 帶來的價值沒那麼大,還有額外的管理成本,大都有被 Cloudwatch Logs Insights 替代,但是 Dashboard 的功能則是兩者都蠻難用的,最終轉移到 Grafana 以後把所有 Metrics + Logs 都統整成統一的報表,整個瀏覽體驗提升加上監控知識更完整的被移轉到系統上,在 Dashboard 上已經達到前所未有的高度了😻
別有洞天
到了上述的狀態,已經幾乎解決了八九成的架構不足引發的痛點了,但是當團隊分工更細以後,也產生了一些開發維護管理的問題,上百個工作的異常監控,在這個架構下少了一個核心的管理中心,所有工作透過 Cloudwatch 觸發 ECS task,代管過頭反而沒有一個可直接涉入的流程,所有異常監控都倚賴這些服務的串接要擴充都要另外接 monitor 來達成
雖然這是一開始希望達成減少管理成本的架構,但是當希望更即時的監控跟統計某些排程的狀態的時候,反而少了一些檢核點,而我們又不想放棄 ECS Task 帶來的優點,所以在近期嘗試換成透過 Rundeck 作為排程的控制中心,取代 Cloudwatch Events 的工作,由一台不需要太多資源的機器來中央控管的觸發 ECS Task,並且收集所有工作的執行狀態跟 task 執行結果,達到可中央控管又不需要承擔太多運算負擔,降低其一開始會因為擔心排程中心處理工作過載引發工作雪崩的風險
還有更良好的 UI 可以人工觸發工作排程 (優於人工觸發 ECS Task 所需的知識),完整監控每一個 Task 的執行結果,並視覺化的檢查哪些工作成功或失敗
結論
這個演化過程累積了近十年的時間,也跟當初團隊技術選擇的文化很有關係,如果當初是用 K8S ,或許不需要那麼複雜的排列組合才能達成這個結果,但是在只有 ECS 的時代,也是沒有更適合的替代方案,而我也不傾向採用過度複雜的架構來解決簡單的問題,但是在技術發展成熟的現在,重新評估一次或許也有不ㄧ樣的結果
但是所有架構跟技術的選擇,都不太可能在一個沒任何包袱的環境下做出來的,所以每個團隊在不同階段當下能做出的判斷都不太一樣,不代表此解法適合其他團隊
技術架構跟組織都要在現有的人的經驗跟結構下,做出持續做出合適的評估跟調整,不放棄持續改進的機會,最終還是能找到一個堪用的解決方案