[教學] 使用 Rsnapshot 達成伺服器自動備份
「不管你的 RAID 有多優秀,都不能阻擋使用者的錯誤」—— 對每一個系統管理員而言,如何定時備份運作中的伺服器,絕對是值得花時間研究的問題。這次介紹的 Rsnapshot 透過設定檔的編寫,可以達成其他工具沒有的功能!
安裝 & 機能簡介
先從安裝開始,主流的發行版應該都可以在預設的套件庫找到:
sudo apt install rsnapshot
注意:在開始之前,請先備份原本的設定:
sudo cp /etc/rsnapshot.conf /etc/rsnapshot.conf.default
與上次介紹的 rdiff-backup 不同的是,rsnapshot 工具仰賴的是設定檔(/etc/rsnapshot.conf
)來運作,大致上看起來如下,因為可以自訂的內容相當多,後面會分段介紹:
################################################# # rsnapshot.conf - rsnapshot configuration file # ################################################# # # # PLEASE BE AWARE OF THE FOLLOWING RULE: # # # # This file requires tabs between elements # # # ################################################# ####################### # CONFIG FILE VERSION # ####################### config_version 1.2 ########################### # SNAPSHOT ROOT DIRECTORY # ########################### # All snapshots will be stored under this root directory. # snapshot_root /var/cache/rsnapshot/ ... 略 ...
與 rdiff-backup
的差異
在不久前,我們介紹了 rdiff-backup 的使用方法,它非常容易使用,幾乎可以說是加了額外功能的 cp
指令。搭配 crontab
等排程工具就能達成各種複雜的功能。幾乎可以說,Rsnapshot
做得到的,沒什麼是 rdiff-backup
做不到的,幾乎。
要說與 rdiff-backup
最大的不同 ,就是備份檔的管理功能,它可以設定不類型的備份(e.g. 每小時備份、每日備份),保留不同的數量。舉例來說,我們可以設定當天逐小時備份(共 24 份),超過一天之後變成每日備份,保留一週(共 7 份);藉此節省空間:
retain hourly 24 retain daily 7
▲ 這邊的設定我們稍後再詳細解釋
從使用設定檔這點也可以看得出來,Rsnapshot
的設計哲學有些不同,它並不仰賴每次呼叫時帶入的參數,而是預先決定好備份的方針,並以 daemon
的方式運作(儘管不會自動將自己設定為系統服務,後面會解釋)。
從一個計畫開始
在本篇的範例中,我們備份的目標是備份 /fileserver
到 /backup
。為了方便想像,可以把 /fileserver
當作是公司裡的公用資料區。為了降低有人誤刪資料時,工作進度上的損失,我們設定每 10 分鐘備份一次,總共保留一個小時(共 6 份);超過一個小時之後,變成每小時備份,保留一天(共 24 份);最後再加上每日備份,保留一個月,照顧一下比較罕用的檔案。
TL;DR:
- 備份
/fileserver
–>/backup
- 每 10 分鐘備份,保留一小時
- 每小時備份,保留一天
- 每日備份,保留一個月
如何設定 /etc/rsnapshot.conf
首先必須設定的是備份目錄(snapshot_root
),對應到備份目的地 localhost/
,這台電腦上所有的備份都會放在這裡,在這個例子中,我們設定為 /backup
:
########################### # SNAPSHOT ROOT DIRECTORY # ########################### # All snapshots will be stored under this root directory. # snapshot_root /backup
接下來,是要備份的目標(把 /fileserver
備份到 /backup/localhost
,名稱可以自訂):
############################### ### BACKUP POINTS / SCRIPTS ### ############################### # LOCALHOST backup /fileserver/ localhost/
設定完來源和目的地之後,我們接著就可以手設定備份檔的保存。這邊分成三種 10_min
、hourly
、daily
三種:
######################################### # BACKUP LEVELS / INTERVALS # # Must be unique and in ascending order # # e.g. alpha, beta, gamma, etc. # ######################################### retain 10_min 6 retain hourly 24 retain daily 30
注意:這邊 retain
的名稱可以自訂,並沒有特殊意義,舉例來說,也可以命名成 alpha
、beta
等不相關的名稱。
設定完成之後,可以下 configtest
檢查一下:
$ sudo rsnapshot configtest Syntax OK
然後就可以透過底下的指令來讓 rsnapshot
開始備份了。可以看得出來,這邊的 10_min
對應得就是 retain
裡面的 10_min
:
sudo rsnapshot 10_min
理想上,我們會希望每 10 分鐘執行一次這個指令,不過這邊我們先看一下執行的結果,後面再來解釋如何設定自動執行:
$ ls -l /backup total 4 drwxr-xr-x 3 root root 4096 Sep 15 10:57 10_min.0
整個 /fileserver
資料夾確實被備份了:
$ tree /backup /backup └── 10_min.0 └── localhost └── fileserver └── time.txt 3 directories, 1 file
注意:不同階層的備份必須要照順序排,從最頻繁到最稀疏。否則可能會出現類似下面的錯誤訊息(後面會解釋):
$ sudo rsnapshot hourly /backup/daily.29 not present (yet), nothing to copy
Rsnapshot 的機制
在解釋 Rsnapshot 的機制之前,首先必須複習一下 Hard Link 的概念。假設我今天建立一個 random_file_link
連結到某個檔案 random_file
,會發生什麼事呢:
ln random_file random_file_link
答案是:兩個檔案會被視為相同的東西:
$ ls -l random_file* -rw-rw-r-- 2 alexleo alexleo 766 Sep 15 11:36 random_file -rw-rw-r-- 2 alexleo alexleo 766 Sep 15 11:36 random_file_link
注:有興趣研究更深入的話可以查查
inode
,或直接下ls -i
看看
簡單來說,我們可以把檔案系統理解成一張表,每個條目紀錄一個檔案,對應到硬碟上的一個(虛擬的)位置。而建立 Hard Link 的意思就是多新增一筆紀錄,對應到相同的檔案。下 rm
刪除的時候也是一樣,我們刪除的只是一筆紀錄,即使所有紀錄都刪除,也不代表它已經不存在硬碟上。(有學過指標/物件程式語言的話,可能會覺得熟悉)
快照循環:
Rsnapshot 就是利用 Hard Link 來保存過去的檔案,每次的備份都會建立一個資料夾,假如我們在 rsnapshot.conf
中設定了以下選項。第一次備份時會建立一個 10_min.0
的資料夾,第二次備份時,原本的 10_min.0
會變成 10_min.1
,然後第二次備份的結果會放進 10_min.0
中。換句話說,每次備份的時候會建立一個 10_min.X
的資料夾,從 0
開始一直遞增,分別是最新到最舊的一次紀錄。
retain 10_min 6
Hard Link 與 快照:
每個資料夾中,使用 Hard Link 的方式儲存著該時間點的檔案;也因為使用 Hard Link 的緣故,若是相同(沒有被修改過)的檔案就不會佔用而外的空間。反過來說,若是一個大小 1GB 的檔案被修改了 1KB,我們也同樣需要備份整個 1G 的檔案,包含過去的 1G 和現在的 1G。算是跟使用 diff
的工具(e.g. rdiff-backup
)不同的地方。
以 Rsnapshot 來說,第一份備份會是使用 rsync
備份,後續的備份則使用 Hard Link 達成(cp -l
),可以使用 -t
選項列出實際會執行的動作(以下使用 sudo rsnapshot -t 10_min
):
echo 6286 > /var/run/rsnapshot.pid /bin/rm -rf /backup/10_min.5/ mv /backup/10_min.4/ /backup/10_min.5/ mv /backup/10_min.3/ /backup/10_min.4/ mv /backup/10_min.2/ /backup/10_min.3/ mv /backup/10_min.1/ /backup/10_min.2/ /bin/cp -al /backup/10_min.0 /backup/10_min.1 /usr/bin/rsync -a --delete --numeric-ids --relative --delete-excluded \ /fileserver/ /backup/10_min.0/localhost/ touch /backup/10_min.0/
快照階層 / 為什麼 retain
必須按照階層排序:
前面我們已經知道快照是按 name.0
~ name.X
的名稱循環的。
這點 很重要 ,rsnapshot 並不會去紀錄那一次的備份是在什麼時間發生的,而且紀錄檔中也沒有辦法指定這樣的資訊(10_min
、hourly
等名稱是只是我們取的名子)。因此,下一層的快照實際上,是取用上一層的最後一個,當成該次的結果(e.g. 10_min.5
變成 hourly.0
)。這也就是為什麼 10_min
的個數只能指定為 6
的原因,因為總和必須要剛好是一個小時啊!
retain 10_min 6 retain hourly 24
如何設定自動執行(crontab
)
因為解釋 crontab
的文章相當豐富,而且也相當容易閱讀,這邊就不挑戰了,放個表格就好了。有興趣的各位,請參考 G. T. Wang 或 鳥哥 的文章!
從 crontab.5
借來的(Ps. 新版的已經拿掉這張圖了):
# Example of job definition: # .---------------- 分鐘 (0 - 59) # | .------------- 小時 (0 - 23) # | | .---------- 日(日期) (1 - 31) # | | | .------- 月(日期) (1 - 12) 或 jan,feb,mar,apr ... # | | | | .---- 日(周) (0 - 6) (Sunday=0 or 7) 或 sun,mon,tue,wed,thu,fri,sat # | | | | | # m h dom mon dow usercommand
因為我們要備份(存取)來自不同使用者的檔案,這邊直接用 root
執行就好:
sudo crontab -e
舉例來說,我的設定長這樣:
*/10 * * * * /usr/local/bin/rsnapshot 10_min 01 * * * * /usr/local/bin/rsnapshot hourly 02 00 * * * /usr/local/bin/rsnapshot daily
另外加了一行,每分鐘寫入一行日期到 /fileserver/time.txt
:
* * * * * date | tee -a /fileserver/time.txt
可以用 journalctl
檢查 crontab
的執行狀況:
$ sudo journalctl -u cron.service -b | grep "CMD" ...略... Sep 16 23:00:01 AlexLeo-VPC CRON[6739]: (root) CMD (rsnapshot 10_min) Sep 16 23:01:01 AlexLeo-VPC CRON[6753]: (root) CMD (rsnapshot hourly) Sep 16 23:10:01 AlexLeo-VPC CRON[6803]: (root) CMD (rsnapshot 10_min) Sep 16 23:20:01 AlexLeo-VPC CRON[6866]: (root) CMD (rsnapshot 10_min) Sep 16 23:30:01 AlexLeo-VPC CRON[6926]: (root) CMD (rsnapshot 10_min) Sep 16 23:40:01 AlexLeo-VPC CRON[6999]: (root) CMD (rsnapshot 10_min) ...略...
如何設定自動執行(systemd
)
畢竟參考來源之一的 ArchWiki 都提到了,就順便紀錄一下吧!
Systemd 的試定檔(Unit)常見於以下幾個地方(詳細請參考 systemd.unit(5)):
/etc/systemd/system
:系統管理員設定的 Unit/usr/local/lib/systemd/system
:系統管理員手動安裝的 Unit/usr/lib/systemd/system
:套件自動安裝的 Unit
命名規則如下:
# .----------------- 單元(Unit)的名稱 # | .----------- 帶有 @ 的 Unit 就是範本(e.g. [email protected]),@string 就是它的實例 # | | .---- 單元(Unit)類型,常見的有 .service、.timer、.sockets 等 # | | | # [email protected]
首先建立一範本檔案 [email protected]
,這邊的 %I
會把 @
後面的東西帶進來(e.g. 執行 [email protected]
會讓 %I
變成 hourly
),更多細節請參考 systemd.unit(5)。
為了避免影響到其他日常工作,我們把 CPU 和 I/O 的優先權調低:
Nice=19
:CPU 優先權,預設為0
,從高到低-20
~+19
IOSchedulingClass=idle
:I/O 優先權,預設為best-effort
,從高到低realtime
、best-effort
、idle
建立 /etc/systemd/system/[email protected]
:
[Unit] Description=rsnapshot (%I) backup [Service] Type=oneshot Nice=19 IOSchedulingClass=idle ExecStart=/usr/bin/rsnapshot %I
另外是定期自動執行的部份,這邊使用 OnCalendar
來指定,它接受使用 Timestamp 格式指定的觸發條件。指定重複觸發條件的方式類似 crontab
,同樣使用星號(*
)除號(/
)等 符號來表示。詳細請參考 systemd.time(7)。
Timestamp 的格式看起來像這樣:
# .--------------------------------------- 日(周)大小寫或縮寫皆可(e.g. Wednesday = Wed = wed) # | .------------------------------- 西元年 # | | .-------------------------- 月(日期) (1 - 12) # | | | .--------------------- 日(日期) (1 - 31) # | | | | .----------------- 小時 (0 - 23) # | | | | | .---------- 分鐘 (0 - 59) # | | | | | | .---- 秒 (0 - 59) # | | | | | | | # DayOfWeek Year-Month-Day Hour:Minute:Second
大概解釋一下各個選項的意義:
OnCalendar=*-*-* *:0/10:00
:指定出發的條件。這邊的0/10
代表每 0, 10, 20, 30, 40, 50 分別都會觸發一次。Persistent=true
:當系統因為某些原因錯過排定的工作時(e.g. 關機),是否立即(補)執行。[email protected]
:因為我們的.service
是範本,沒辦法直接對應timer
,所以要用Unit
引數手動指定
建立 /etc/systemd/system/rsnapshot-10_min.timer
:
[Unit] Description=rsnapshot 10_min backup [Timer] # Run every 10 min OnCalendar=*-*-* *:0/10:00 Persistent=true Unit=rsnapshot@10_min.service [Install] WantedBy=timers.target
建立 /etc/systemd/system/rsnapshot-hourly.timer
:
[Unit] Description=rsnapshot hourly backup [Timer] # Run hourly OnCalendar=*-*-* *:01:00 Persistent=true [email protected] [Install] WantedBy=timers.target
建立 /etc/systemd/system/rsnapshot-daily.timer
:
[Unit] Description=rsnapshot daily backup [Timer] # 00:02 is the clock time when to start it OnCalendar=*-*-* 00:02:00 Persistent=true [email protected] [Install] WantedBy=timers.target
列一下剛才新增的 Unit 設定檔:
$ sudo systemctl list-unit-files | grep rsnapshot [email protected] static enabled rsnapshot-10_min.timer disabled enabled rsnapshot-daily.timer disabled enabled rsnapshot-hourly.timer disabled enabled
啟用 timer
:
sudo systemctl enable --now rsnapshot-10_min.timer sudo systemctl enable --now rsnapshot-hourly.timer sudo systemctl enable --now rsnapshot-daily.timer
遠端主機
首先確定 Rsnapshot 主機上有建立好 SSH 金鑰對,沒有的話請使用 ssh-keygen
工具建立:
root@AlexLeo-VPC:~# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Created directory '/root/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/id_rsa Your public key has been saved in /root/.ssh/id_rsa.pub The key fingerprint is: SHA256:Mm4E/Qn1ZXOqxlcCjph/6LAmKm0Mk0XSTol0TOIdTrI root@AlexLeo-VPC The key's randomart image is: +---[RSA 3072]----+ |.*o* . . + . | |+ &... + + + + | | E o. = . o o . | | o . + + . o | | o = S = . | |+ o * o . | | = . = . | |. +. + | | o. | +----[SHA256]-----+
複製你的公鑰到遠端主機:
# << 檢視 Rsnapshot 主機的公鑰 << sudo cat /root/.ssh/id_rsa.pub # >> 在遠端主機,新增信任的金鑰 >> sudo vim /root/.ssh/authorized_keys
試著連線遠端主機(本例為 192.168.1.46
):
root@AlexLeo-VPC:~# ssh 192.168.1.46 The authenticity of host '192.168.1.46 (192.168.1.46)' can't be established. ECDSA key fingerprint is SHA256:OKnlK1OCF+17uUAjdIQQ3gavXE1pQpbotWRQADZvQVI. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes ...略...
因為 Rsnapshot 遠端連線需要,取消註解 cmd_ssh
啟用它:
# Uncomment this to enable remote ssh backups over rsync. # cmd_ssh /usr/bin/ssh
加入遠端主機到設定檔裡面,這邊使用 root
登入 192.168.1.46
,備份 /home
到本機的 /backup/remote/
:
backup [email protected]:/home/ remote/
過了一段時間之後,備份的檔案就出現在 /backup
資料夾裡面了:
$ tree -L 3 /backup /backup └── 10_min.0 ├── localhost │ └── fileserver └── remote └── home
注:提醒一下,本例的設定檔包含有兩個來源:
backup /fileserver localhost/ backup [email protected]:/home/ remote/
額外的自訂備份腳本
Rsnapshot 的特色就是提供單一的工具,集中管理所有的備份設定。想當然爾,為了對應不同的應用,單純的檔案備份,常常是不夠的,比如說:備份 MySQL 資料庫,我們需要自訂一些腳本!
在設定檔中加入新增一行 backup_script
:
backup /fileserver localhost/ backup [email protected]:/home/ remote/ backup_script /usr/local/bin/backup_mysql.sh mysql/
建立 /usr/local/bin/backup_mysql.sh
,它會呼叫 mysqldump
來備份資料庫:
#!/bin/sh /usr/bin/mysqldump --user=root --all-databases > mydatabase.sql
這邊解釋一下,Rsnapshot 在執行腳本的時候,會自動建立一個暫時的資料夾(tmp
),待腳本執行完成之後,會再複製暫時資料夾中的結果到備份的目的地(mysql/
)。
$ sudo rsnapshot -t 10_min ...略... mkdir -m 0755 -p /backup/tmp/ cd /backup/tmp/ /usr/local/bin/backup_mysql.sh cd /backup/ sync_if_different("/backup/tmp/", "/backup/10_min.0/mysql/") ...略...
備份成功:
$ tree -L 3 /backup /backup └── 10_min.0 ├── localhost │ └── fileserver ├── mysql │ └── mydatabase.sql └── remote └── home
參考資料
- rsnapshot – ArchWiki
- 非常完整的介紹,再次感受到了 Arch 社群的吸引力!
- rsnapshot/rsnapshot: a tool for backing up your data using rsync – GitHub
- 官方的 GitHub Repo,提到
-t
跟configtest
- 官方的 GitHub Repo,提到
- backup – explain rsnapshot incremental rotation – Server Fault
- 快照原理(Hard Link)的部份,參考了 Poohl 的回答
- systemd.unit(5) — Arch manual pages
- 這邊有解釋 Systemd Unit 檔案在不同位置的意義
- systemd.exec(5) — Arch manual pages
- 這邊提到 systemd 的
IOSchedulingClass
引數接受的數值
- 這邊提到 systemd 的
- ionice(1) — Arch manual pages
- 這邊對 I/O 排程的
IOSchedulingClass
有比較詳細的解釋
- 這邊對 I/O 排程的
- systemd.time(7) — Arch manual pages
- 這邊有解釋 Systemd 的 Calander Events 格式
- systemd.timer(5) — Arch manual pages
- 這邊有解釋
Persistent
引數的意義
- 這邊有解釋
注:Rsnapshot 的圖樣屬於該開源專案,本文基於描述需要合理使用。 (Note: Usage of Rsnapshot banner logo here is for the purpose of describing only.)