實體主機備份儲存庫完整性:用 restic 驗證讀取、還原與損壞偵測

實體主機備份儲存庫完整性:用 restic 驗證讀取、還原與損壞偵測

備份工作顯示成功,還原時卻讀不出檔案,問題可能出在備份完成後發生的儲存資料損壞。只核對快照數量,可以確認工作是否執行,卻無法證明資料封包仍然完整。儲存管理人員需要三類彼此獨立的證據:儲存庫結構正確、所有資料皆可讀取,以及還原內容與基準一致。

本文使用 Debian 12、restic 與 Python 3,在暫存目錄中完成正常狀態檢查與損壞偵測。不讀取正式環境資料、不格式化磁碟,也不修改現有備份儲存庫。本實驗適用於實體主機上的檔案備份;運作中的資料庫應先透過資料庫本身的機制產生一致性備份,再進入檔案備份流程。

一、先釐清各項檢查能證明什麼

restic 將檔案內容組織成加密的資料封包,並維護快照、索引與參照關係。一般的 check 主要檢查儲存庫結構;check --read-data 則會進一步讀取並驗證所有資料封包。兩者不能互相取代,還原測試也不可省略。

還原比對的重點,是選定的快照能否在空目錄中重建預期檔案,而不是確認儲存庫內每個歷史資料封包都完整。反過來說,即使完整讀取成功,也不能證明備份時選對了目錄、排除規則未誤排所需資料,或應用程式資料本身具有一致性。

鏡像可在部分裝置故障時維持存取,備份用來保留歷史版本,完整性檢查則負責找出異常;三者都不能取代實際還原。

OnuCloud 的 AMD 實體主機標準配備兩顆 1TB NVMe,各自直接連接,沒有硬體 RAID。將來源目錄與備份儲存庫分別放在同一台主機的兩顆磁碟上,可以隔離部分單碟故障,但這並不是位於獨立故障域的異地備份。以下的本機儲存庫僅用於驗證操作流程。

二、建立可重複執行的隔離樣本

先安裝工具,接著在同一個 Bash 工作階段中執行實驗命令,避免暫存變數遺失。安裝需要管理員權限,實驗本身使用一般帳號即可。

sudo apt-get update
sudo apt-get install restic python3

umask 077
LAB=$(mktemp -d /var/tmp/restic-lab.XXXXXXXX)
export LAB
export RESTIC_REPOSITORY="$LAB/repository"
export RESTIC_PASSWORD_FILE="$LAB/password"

mkdir -p "$LAB/source"
python3 - <<'PY'
import os
import secrets
from pathlib import Path

root = Path(os.environ["LAB"])
(root / "password").write_text(secrets.token_hex(32) + "\n")
(root / "source" / "record.txt").write_text(
    "backup verification\n", encoding="utf-8"
)
(root / "source" / "payload.bin").write_bytes(os.urandom(1024 * 1024))
PY

restic version
restic init
(
  cd "$LAB"
  restic backup source
)
restic snapshots

隨機檔案可避免所有樣本都被壓縮成極小的資料,同時確保儲存庫中有實際內容。密碼檔位於儲存庫目錄之外,因此複製儲存庫時不會連同解密憑證一起複製。

這裡只有一份快照,後續可以使用 latest。正式環境應記錄明確的快照 ID,否則檢查期間若有新備份完成,還原目標可能隨之改變。不要將這份暫時的實驗密碼視為已完成正式的金鑰保管作業;正式儲存庫需要獨立保存、且確實能取回的解密憑證。

三、分別驗收完整讀取與還原內容

先執行結構檢查,再讀取所有資料封包。不要直接將輸出接到未處理結束碼的管線,否則最後一個命令執行成功,可能會掩蓋檢查失敗。

restic check
restic check --read-data
restic restore latest --target "$LAB/restored"
diff -r -- "$LAB/source" "$LAB/restored/source"

四個步驟都應以零結束碼完成,diff 在結果一致時不會輸出差異。目前的樣本只包含一般檔案,因此比對內容與目錄就已足夠;實際備份還應另外核對擁有者、權限、連結關係,以及應用程式所需的延伸屬性。

還原必須寫入新的空目錄,不可覆寫來源檔案。否則舊檔案可能掩蓋漏備份的情況,新還原的資料也可能污染正式環境目錄。完成後應保留快照 ID、工具版本、檢查輸出與比對結果;只留一張「工作成功」的螢幕擷取畫面,不足以事後查核實際還原的對象。

如果 check 已經失敗,就應停止後續的正常驗收並保留現場狀態。若結構檢查成功、完整讀取卻失敗,則應優先調查資料封包的讀取與內容完整性,而不是因為快照清單正常就繼續使用儲存庫。

四、只對副本注入損壞,確認檢查會拒絕通過

負向測試是為了證明整套檢查流程確實能發現問題。先停止寫入實驗儲存庫,再複製一份獨立副本。禁止使用硬連結複製,否則修改副本時,可能同時改動原始儲存庫。以下命令明確停用寫入時複製;實驗資料量很小,便於理解兩份資料彼此獨立的界線。

cp -a --reflink=never "$LAB/repository" "$LAB/damaged"

python3 - <<'PY'
import os
from pathlib import Path

data = Path(os.environ["LAB"]) / "damaged" / "data"
pack = next(p for p in sorted(data.rglob("*")) if p.is_file())
with pack.open("r+b") as f:
    f.seek(pack.stat().st_size // 2)
    pos = f.tell()
    original = f.read(1)
    if not original:
        raise RuntimeError("Empty data pack")
    f.seek(pos)
    f.write(bytes([original[0] ^ 1]))
print(pack.name)
PY

if restic -r "$LAB/damaged" check --read-data \
  >"$LAB/damaged-check.log" 2>&1; then
  printf '%s\n' 'UNEXPECTED_SUCCESS'
else
  printf '%s\n' 'EXPECTED_FAILURE'
fi
cat "$LAB/damaged-check.log"
restic check --read-data

預期副本的檢查會以非零結束碼結束,且日誌指出資料封包損壞;原始儲存庫的檢查則仍然成功。不能只看指令碼印出的失敗標記,還必須確認失敗原因確實是資料損壞,而不是密碼錯誤、路徑錯誤或權限不足。

這一步證明的是能偵測到損壞,不是能修復損壞。資料封包中的某個位元組改變,可能影響其中一個或多個物件;不要據此推算正式環境事故的受影響比例,也不要再將這份副本用作備份目標。

五、將實驗結論轉化為正式環境的檢查界線

完整讀取會占用磁碟吞吐量,遠端儲存庫還會消耗網路頻寬。執行前應記錄儲存庫大小、檢查範圍與服務負載,選擇低負載時段,並觀察服務延遲。不限流量不代表傳輸速度無上限,也不表示完整性掃描沒有資源成本。

大型儲存庫可以分批讀取資料封包,但必須記錄每次涵蓋的範圍,確保最終涵蓋所有資料;一次抽樣成功,不能記錄為整個儲存庫通過檢查。檢查工作也應避免在未經規劃的情況下,與清理、修復或其他會變更儲存庫內容的操作同時執行。

發現損壞後,先暫停會修改儲存庫的工作,保留日誌與副本,再檢查檔案系統、裝置與傳輸鏈路。不要立即執行 forget、prune 或修復命令,以免減少可供還原與調查的證據。

最終驗收應同時具備三項結果:正常儲存庫可完整讀取、指定快照可還原且通過內容比對,以及受損副本被明確判定為不通過。實驗檔案可在保存紀錄後,由人工核對路徑再刪除;密碼、日誌與還原樣本都屬於需要受控處理的資料。

常見問題

restic check 成功是否代表所有備份資料都已讀過?

不是。一般 check 主要檢查儲存庫結構與參照關係;加上 --read-data 才會讀取並校驗全部資料包。完整檢查成功後,仍應另外執行隔離還原與內容比對。

發現儲存庫損壞後可以直接清理嗎?

不應立即執行 forget、prune 或修復操作。先暫停會修改儲存庫的工作,保留紀錄與儲存庫副本,排查儲存與傳輸路徑,再從獨立副本驗證可還原的資料。

AMD EPYC 7543P 獨立實體主機

$472/月起

設定 OnuCloud 實體主機 查看 OnuCloud 租用價格

依工作負載選擇實體主機配置

AMD EPYC 7543P 獨享整機;儲存備援與資料備份由管理員自行規劃。