『Linux/Debian』 zfsファイルシステムのsnapshotを取ったり適当に間引いたりするスクリプトを造った

運用をはじめたzfsなファイルサーバーですが、zfsの大きな特徴の一つにsnapshot機能があります。
詳しい説明はちゃんと開設したサイトで調べて欲しいところですが、私が理解しているところのメリットは
・ファイルを操作した履歴を残せる(解りやすく言えば、AppleのTimeMachineみたいなヤツ、削除したファイルを復活させたりできる)
・別Disk、別マシーンにバックアップを取るときにはsnapshot機能を使うと素敵な状態になる
です。

snapshotを使ってバックアップと言われるのは、履歴を残せることだと思っているのですが、同じDiskにバックアップを残してもそれはバックアップじゃないので、別にバックアップは取っておく必要はありますね。

snapshotを取る戦略として
1.1時間おきにsnapshotを取る
2.ファイルサーバーは24時間運用では無い。電源のON/OFFあり
2.翌日になったら、その日から見て直近のサーバーが動いていた日にちのsnapshotは、その日のうちの一番新しいsnapshotのみを残して削除する
3.毎週日曜日に、その週の前の日曜日を起点に古い分6日分のsnapshotを削除する
4.毎月1日に、先々月のsnapshotを一番新しいsnapshot以外削除する。
5.毎月1日に、8ヶ月前のsnapshotを削除する。
としました。

ファイルサーバーが動いていない日にちがある可能性を考えると、3,4の条件はもうちょっと細かくしてやらないとダメなのですが、そこはanacronを入れてやることでカバーできる部分はカバーしてもらって、あとは手動運用で何とかする方針です。
基本的には、1週間以上サーバーの電源を落とす事は希なはず(盆と正月くらい)って前提です。

この方針で、
・当日は1時間おきのsnapshotが作られる。
・前日より前の1週間分は、1日分のsnapshotが残る
・1~4週間分は日曜日時点のsnapshotが残る
・2~7ヶ月前の月末に近い日曜日のsnapshotが残る
ことになるので、誤ってファイルを消しちゃった場合のカバーはできるかなと。
ぶっちゃけ、個人ユースなら1時間おきのsnapshotを60こ位残しておけば十分だと思うんですがね

以上の方針でスクリプトを作りました。
本当は誰かが公開しているものをそのまま使おうと思ったのですが、自分にぴったりのモノがなかったので書きました。
それぞれ、/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthlyに放り込んでおきます。
/etc/cron.*ディレクトリに置いておくファイル名は、”.”を含んだファイルは実行されないんですね。
hogehoge.shって名前にしていてcronで実行されずにものすごく悩みました。

もしかしたら、似たような運用をしたい人が居るかもしれないので、スクリプトを公開しておきます。
rubyスクリプトです。
githubで公開するのが今時なんだと思いますが、アカウント持っていないので貼り付けます(汗

ちょっと冗長なところ等があったりしますが、何かの参考になれば。

/etc/cron.hourly/zfs_snapshot_hourly
[bash]
#!/bin/bash

zfs snapshot -r vol1/pub1@`date ‘+%Y%m%d-%H%M’`
zfs snapshot -r vol1/pub2@`date ‘+%Y%m%d-%H%M’`
[/bash]
これだけはbashスクリプトです。
他のDiskへバックアップを取るvol1/pub1とバックアップを取らないvol1/pub2に分けて、それぞれsnapshotを取ることにしています。

/etc/cron.daily/zfs_snapshot_daily
[ruby]
#!/usr/bin/ruby
#本日より前で最も近い日にちのsnapshotについて、
#その当日内で最も時間的に新しいsnapshoto以外を
#削除する

require “open3”

#zfs snapshot一覧を取得
stdin,stdout,stderr = Open3.capture2(‘zfs list -o name -S creation -t snapshot’)
snapshot_list = stdin.split(“n”)
snapshot_list.delete_at(0)

#処理対象のsnapshot listを作成
#処理対象日を絞り込む
require “date”
today = Date.today
beforeday = today – 1
beforeday_str = beforeday.strftime(“%Y%m%d”)

target_day_flag = false
while !target_day_flag do
snapshot_list.each do |s|
target_day_flag = target_day_flag | s.include?(beforeday_str)
end
if !target_day_flag then
beforeday = beforeday – 1
end
beforeday_str = beforeday.strftime(“%Y%m%d”)

end

#処理対象のリストをpub1とpub2でそれぞれ造る
def extract_target_snapshot_list(list, target_name)
l = Array.new
list.each do |s|
if s.include?(target_name) then
l.push(s)
end
end
return l
end

pub1_snapshot_list = Array.new
pub1_snapshot_list = extract_target_snapshot_list(snapshot_list,”vol1/pub1@”+beforeday_str)

pub2_snapshot_list = Array.new
pub2_snapshot_list = extract_target_snapshot_list(snapshot_list,”vol1/pub2@”+beforeday_str)

#リストを整理して、一番新しい時間だけを除く
pub1_snapshot_list.sort!
pub1_snapshot_list.pop
pub2_snapshot_list.sort!
pub2_snapshot_list.pop

#削除対象snapshotを削除する

if !pub1_snapshot_list.empty? then
pub1_snapshot_list.each do |s|
stdin,stdout,stderr = Open3.capture2(‘zfs destroy ‘+s)
end
end

if !pub2_snapshot_list.empty? then
pub2_snapshot_list.each do |s|
stdin,stdout,stderr = Open3.capture2(‘zfs destroy ‘+s)
end
end
[/ruby]

/etc/cron.weekly/zfs_snapshot_weekly
[ruby]
#!/usr/bin/ruby
# -*- coding: utf-8 -*-
#日曜日に実行されることが前提
#実行された日にちが含む週(日曜日始まり)の前の週の
#日曜日を基準に、その日から前の月曜日までの
#snapshotを削除する

require “open3”

#zfs snapshot一覧を取得
stdin,stdout,stderr = Open3.capture2(‘zfs list -o name -S creation -t snapshot’)
snapshot_list = stdin.split(“n”)
snapshot_list.delete_at(0)

#処理対象のsnapshot listを作成
#処理対象日を絞り込む
require “date”
this_week = Date.today

#直近の日曜日を探す
while !this_week.sunday? do
this_week = this_week -1
end

#処理開始の起点はその前の日曜日
last_week = this_week – 7

#処理対象のリストをpub1とpub2でそれぞれ造る
#volume_name 対象ファイルシステム、最後に@はつけない
def extract_target_snapshot_list(list, volume_name, startpoint)
l = Array.new
for i in 1..6 do
w_date = startpoint – i;
d_string = w_date.strftime(“%Y%m%d”)
list.each do |s|
if s.include?(volume_name+”@”+d_string) then
l.push(s)
end
end
end
return l
end

pub1_snapshot_list = Array.new
pub1_snapshot_list = extract_target_snapshot_list(snapshot_list,”vol1/pub1″, last_week)

pub2_snapshot_list = Array.new
pub2_snapshot_list = extract_target_snapshot_list(snapshot_list,”vol1/pub2″, last_week)

#削除対象snapshotを削除する

if !pub1_snapshot_list.empty? then
pub1_snapshot_list.each do |s|
stdin,stdout,stderr = Open3.capture2(‘zfs destroy ‘+s)
end
end

if !pub2_snapshot_list.empty? then
pub2_snapshot_list.each do |s|
stdin,stdout,stderr = Open3.capture2(‘zfs destroy ‘+s)
end
end
[/ruby]

/etc/cron.monthly/zfs_snapshot_monthly
[ruby]
#!/usr/bin/ruby
# -*- coding: utf-8 -*-
#毎月第1日に実行されることが前提
#実行された月の先々月のsnapshtについて、
#一番新しいもの以外削除する

#プラス
#6ヶ月分を残して、残りのスナップショットは削除する

require “open3”

#zfs snapshot一覧を取得
stdin,stdout,stderr = Open3.capture2(‘zfs list -o name -S creation -t snapshot’)
snapshot_list = stdin.split(“n”)
snapshot_list.delete_at(0)

#処理対象のsnapshot listを作成
#処理対象日を絞り込む
require “date”
today = Date.today

#先々月の日付を取得する
target_month = today << 2 target_month_str = target_month.strftime("%Y%m"); #処理対象のリストをpub1とpub2でそれぞれ造る #volume_name 対象ファイルシステム、最後に@はつけない def extract_target_snapshot_list(list, volume_name, start_point) l = Array.new list.each do |s| if s.include?(volume_name+"@"+start_point) then l.push(s) end end return l end pub1_snapshot_list = Array.new pub1_snapshot_list = extract_target_snapshot_list(snapshot_list,"vol1/pub1", target_month_str) pub2_snapshot_list = Array.new pub2_snapshot_list = extract_target_snapshot_list(snapshot_list,"vol1/pub2", target_month_str) #リストを整理して、一番新しい時間だけを除く pub1_snapshot_list.sort! pub1_snapshot_list.pop pub2_snapshot_list.sort! pub2_snapshot_list.pop #削除対象snapshotを削除する if !pub1_snapshot_list.empty? then pub1_snapshot_list.each do |s| stdin,stdout,stderr = Open3.capture2('zfs destroy '+s) end end if !pub2_snapshot_list.empty? then pub2_snapshot_list.each do |s| stdin,stdout,stderr = Open3.capture2('zfs destroy '+s) end end #8ヶ月前のsnapshotを削除する target_month = today << 8 target_month_str = target_month.strftime("%Y%m"); eight_month_ago__snapshot_list = Array.new snapshot_list.each do |s| if s.include?(target_month_str) then eight_month_ago__snapshot_list.push(s) end end if !eight_month_ago__snapshot_list.empty? then eight_month_ago__snapshot_list.each do |s| stdin,stdout,stderr = Open3.capture2('zfs destroy '+s) end end [/ruby]

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です