GitHub Actions 排程還是主機 cron:我們的資料庫備份靜靜斷了三天之後
2026 年 8 月 20 日,我們的 GitHub 組織因為 Actions 用量超額、扣款失敗,整個組織的 Actions 被停掉。每天凌晨跑資料庫邏輯備份的排程也在裡面。三個正式資料庫的最後一份備份停在台灣時間 8 月 20 日凌晨 3 點 02 分,之後三天沒有任何一則告警。這篇是那次之後我們怎麼把備份搬回主機 cron、搬的時候又踩了哪些坑,最後附一張可以直接拿去問你的系統商的檢查表。
先講結論
- GitHub Actions 的排程適合跑測試與部署,不適合當備份的唯一排程。它停擺的原因可能跟你的系統完全無關,例如帳單。
- GitHub 官方文件寫明兩件事:帳上沒有有效付款方式時,免費額度用完就會擋下用量;負載高時排程可能延遲,排隊中的 job 甚至可能被丟掉。
- 搬回主機 cron 之後最常見的坑是手動跑得過、半夜跑不過:cron 的 PATH 與 HOME 跟你登入時不一樣。
- 備份腳本要把「看起來成功其實失敗」的情況全部變成失敗:檔案驗證、大小下限、異地上傳失敗都要讓腳本以非零結束。
- 我們目前還缺一塊:失敗會留在 log,但沒有主動通知。這件事寫在文末,沒做完的不假裝做完。
一、那三天發生了什麼
拓 ERP 的正式站與兩個客戶站跑在同一台 EC2 上,資料庫放在 AWS RDS。RDS 本身有執行個體層級的自動快照,我們另外每天做一份 pg_dump 邏輯備份。兩者用途不同:快照只能整台還原回 RDS;邏輯備份可以單獨還原一個資料庫、搬到任何一台 PostgreSQL,也能離線打開來檢查內容。
那份邏輯備份原本由 GitHub Actions 的 schedule 觸發,每天凌晨 SSH 進主機跑 dump。組織是 GitHub Free 方案,私有 repo 每月有 2,000 分鐘的免費額度。8 月 20 日額度用完,扣款沒有成功,整個組織的 Actions 停了兩天。
停的不只是 CI。備份那支 workflow 根本沒有啟動,所以也不會失敗,自然沒有失敗通知。我們在 8 月 22 日才發現最後一份邏輯備份停在 8 月 19 日 19:02 UTC,換算台灣時間是 20 日凌晨 3 點 02 分。三個正式庫都是這樣。
那三天實際上沒有資料遺失,RDS 的自動快照一直在跑。但如果那三天裡主機或 RDS 出了需要邏輯備份才救得回來的事,我們手上最新的可攜備份就是三天前的。
二、為什麼備份不該只掛在 Actions 上
回頭看,問題在於我們把一件「每天一定要發生」的事,交給一個會因為跟它無關的原因停擺的系統。GitHub 的 Actions 計費文件寫得很清楚:「If your account does not have a valid payment method on file, usage is blocked once you use up your quota.」用量被擋的時候,被擋的是整個帳號或組織,不分哪支 workflow 比較重要。
另一個比較少人注意的限制寫在觸發事件文件的 schedule 那一節:負載高時排程可能延遲,「If the load is sufficiently high enough, some queued jobs may be dropped.」也就是說,就算帳單正常,排程也不保證每一次都會執行。公開 repo 還多一條:60 天沒有活動,排程會被自動停用。
這三種狀況有個共通點:workflow 沒有跑,所以不會有任何失敗紀錄。大部分人設的告警是「失敗時通知」,對「沒有發生」這件事完全看不見。
三、GitHub Actions 排程與主機 cron 差在哪
兩邊都能做到每天定時跑一支腳本,差異在它們會因為什麼原因停下來,以及停下來時你看不看得到:
| 比較項目 | GitHub Actions schedule | 主機上的 cron |
|---|---|---|
| 會因為帳單停擺 | 會,額度用完又無有效付款方式時整個組織被擋 | 只有主機本身停機時 |
| 準時程度 | 官方註明高負載時可能延遲或丟掉排隊中的 job | 跟著主機時鐘,準時 |
| 沒跑的時候 | 沒有任何紀錄,也不會觸發失敗通知 | 同樣看不見,除非另外監控 |
| 執行環境 | 每次乾淨的 runner,要另外 SSH 或用憑證連進正式環境 | 就在正式環境裡,但 PATH、HOME 跟登入時不同 |
| 佔用的資源 | 計入 Actions 分鐘數(私有 repo) | 佔主機的 CPU 與磁碟 |
| 適合拿來做 | 測試、建置、部署、手動補跑入口 | 每天一定要發生的維運工作 |
表上「沒跑的時候」那一列兩邊都是看不見,這點搬家不會解決。搬到 cron 解決的是「停擺的原因跟系統無關」,主動監控得另外做,第七節會再講。
四、搬回 cron:兩個半夜才會出現的坑
8 月 22 日我們把原本寫在 workflow 裡的腳本抽成獨立檔案 scripts/backup-db.sh,裝到主機的 /etc/cron.d/,每天 18:30 UTC(台灣凌晨 2 點 30 分)執行。時間是刻意挑的,避開同一台機器上其他系統的備份與磁碟檢查排程,不讓兩支吃 I/O 的工作撞在一起。
隔天補上 S3 異地副本時,撞到兩個典型的 cron 問題,兩個都是手動執行完全正常、交給 cron 才會壞:
- PATH 找不到指令。主機上的 AWS CLI 是用 snap 裝的,位置在
/snap/bin。cron 的預設 PATH 沒有這個目錄,所以半夜執行會得到command not found。解法是在 cron 檔裡把 PATH 寫完整,腳本裡也用找不到就退回絕對路徑的寫法。 - HOME 不對,憑證讀不到。AWS 憑證放在
/home/ubuntu/.aws,cron 的 HOME 不一定指向那裡。指令找得到,但會以沒有憑證的身分去上傳,然後失敗。cron 檔裡要明寫HOME=/home/ubuntu。
驗證方式是用 env -i 清空環境變數再執行一次,模擬 cron 那種什麼都沒有的環境。在登入的 shell 裡直接跑,等於沒測。
原本那支 GitHub workflow 沒有刪,改成只能手動觸發,呼叫的是主機上同一支腳本。這樣還原前想先留一份當下狀態時有入口可按,而且邏輯只有一份,不會變成兩支腳本各改各的。
五、備份腳本該把哪些「成功」當成失敗
這次真正學到的是:備份的風險大多不是「失敗了」,是「看起來成功了」。所以腳本的設計方向是把每一種假成功都轉成非零離開碼:
- 壓縮檔要能解開。每份 dump 寫完立刻跑
gzip -t,壞檔當場失敗。 - 檔案小得可疑就是失敗。連線失敗時
pg_dump可能只吐出幾行錯誤,壓縮後仍是一個合法的 gzip。我們把小於 10 KB 的 dump 一律視為失敗。 - 異地上傳失敗要讓整支腳本失敗。本機那份還在,看起來好像沒事,但「現在只剩本機一份」這件事必須被看見。
- 任一個資料庫失敗,整支就失敗。三個庫跑完兩個成功一個失敗,不能回報成功。
- 還沒建立的實例明講略過。這是唯一一種不算失敗的跳過,而且 log 裡會寫出來。
保留策略分兩層:主機上每個資料庫留最近 14 份,給快速還原用;S3 上每個資料庫一個獨立路徑,30 天後轉到低頻存取、180 天後刪除,bucket 關閉所有公開存取並開啟伺服器端加密。上傳失敗的路徑也測過:故意指向一個不存在的 bucket,確認腳本真的回非零。
六、順便把 Actions 分鐘數砍到原本的三分之一
備份搬走只處理了一半,另一半是不要再超額。9 月底我們重新看了每一支 workflow 花的分鐘數,做了三件事:
- Go 的 race 檢測(
-race)佔掉測試 job 一半以上的時間,從每張 PR 的檢查移到上線前的部署流程跑一次。那才是真的要進正式站的版本。 - 資安掃描不再掛在每張 PR 上,改成每週排程加手動觸發。每週那次不能拿掉,它負責抓「程式沒動,但新公布了漏洞」的情況。
- 同一份程式碼不付兩次錢:驗證類 workflow 只在 PR 觸發,合併之後不再對同一個 commit 重跑一次。為了不讓這條規則被後來的人加回去,我們寫了一支檢查腳本,在本機 commit 與 push 前自動擋下重複的觸發條件。
結果是一張 PR 的檢查從約 12 分鐘降到約 4 分鐘。直接推到主要分支的驗證,改在本機的 git hook 跑完整版,不佔 runner。
七、還沒做完的事
現在備份失敗時,腳本會以非零結束、錯誤寫進 /var/log/tuoerp-backup.log。但 cron 不會因為非零就通知任何人,所以「失敗了沒人知道」在主機端仍然可能發生,只是機率比之前低。
我們打算補的是反過來的監控:不是「失敗時通知」,而是「超過 26 小時沒有看到新的成功備份就通知」。這種寫法同時抓得到腳本失敗、cron 沒跑、主機停機三種情況,也正好是 8 月那次缺的那一塊。做完會在這篇補上日期。
八、問你的系統商:備份五問
不管你用的是哪一套 ERP,這五題都可以直接拿去問。答不出來,或答案是「應該有」,就值得追問:
- 備份是由什麼排程觸發的?它會不會因為跟你的系統無關的原因停擺,例如 CI 平台的帳單或額度?
- 最近一份成功的備份是什麼時候?請對方當場查出日期與時間。「每天都有」不算答案。
- 有沒有可攜的邏輯備份?雲端主機的快照只能還原回同一家雲端;能匯出到別台資料庫的格式才是換廠商時帶得走的那份。
- 異地副本放在哪?跟正式主機在同一台、同一個帳號,還是不同地方?
- 多久沒有新備份會有人被通知?這題問的是「該發生卻沒發生」時有沒有人知道。
第五題我們自己目前也只答得出一半。把它列在這裡,是因為 8 月那三天讓我們知道,這題答不出來的時候,通常不會有人發現。
常見問題
GitHub Actions 的排程可以拿來做資料庫備份嗎?
可以跑,但不建議當唯一的排程。GitHub 官方文件寫明,帳上沒有有效付款方式時免費額度用完就會擋下用量,負載高時排程也可能延遲或丟掉排隊中的 job。這些情況下 workflow 根本沒啟動,不會有失敗通知。比較穩的做法是由主機 cron 執行,Actions 只留手動補跑入口。
為什麼備份腳本手動執行正常,交給 cron 就失敗?
cron 的環境跟登入的 shell 不同。最常見的是 PATH 少了某些目錄(例如 snap 安裝的 AWS CLI 在 /snap/bin),以及 HOME 沒有指向放憑證的家目錄。在 cron 檔裡把 PATH 與 HOME 寫完整,並用 env -i 清空環境變數再測一次,才算模擬到 cron 的條件。
雲端資料庫已經有自動快照,還需要邏輯備份嗎?
需要。快照只能整台還原回同一家雲端;pg_dump 這類邏輯備份可以單獨還原一個資料庫、搬到任何一台 PostgreSQL,也能離線檢查內容。換廠商或只想救回一個資料庫時,用得上的是邏輯備份。
怎麼知道備份是不是真的有在跑?
設定「超過一段時間沒有新的成功備份就通知」,而不是只設失敗通知。排程沒啟動、主機停機、腳本失敗三種情況,前兩種都不會產生失敗紀錄,只有檢查最近一次成功的時間才抓得到。
想看看這些在實際系統裡長什麼樣?
拓 ERP 把進銷存、會計四表、401 申報所需明細與電子發票處理放在同一套資料上。申請試用可取得 15 天完整功能的體驗環境。