[教學] 使用 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_minhourlydaily 三種:

#########################################
#     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 的名稱可以自訂,並沒有特殊意義,舉例來說,也可以命名成 alphabeta 等不相關的名稱。

設定完成之後,可以下 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_minhourly 等名稱是只是我們取的名子)。因此,下一層的快照實際上,是取用上一層的最後一個,當成該次的結果(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,從高到低 realtimebest-effortidle

建立 /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
[email protected]_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 工具建立:

[email protected]:~# 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 [email protected]
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):

[email protected]:~# 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
    │&nbsp;&nbsp; └── 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
    │&nbsp;&nbsp; └── fileserver
    ├── mysql
    │&nbsp;&nbsp; └── mydatabase.sql
    └── remote
        └── home

參考資料

注:Rsnapshot 的圖樣屬於該開源專案,本文基於描述需要合理使用。 (Note: Usage of Rsnapshot banner logo here is for the purpose of describing only.)

發表迴響