ユニキャストの野口です。
これは ユニキャスト Advent Calendar 2017 の 12/4 の記事です。
ユニキャスト Advent Calendar 2017 – Qiita
Bacula を使った 110 台以上の大量のバックアップジョブを捌くために立ち向かった記録です。
バックアップというミッションに向き合う
バックアップ、してますか?
システム開発は花形であり、どの設計でどの言語、実装、フレームワーク、デプロイ手法、ミドルウェア等々とても華やかなイメージがあります。
バックアップは後ろ向きなイメージが先行しますが、システムを運用する上で欠かせないのがバックアップです。
形あるものはいずれ壊れます。
そういったときにシステムをリカバリ(リストア)するためにバックアップは非常に重要なものです。
それではバックアップ手法にはどんなものがあるか少し見てみましょう。
クラウド
今やクラウド全盛です。Amazon Web Service や Google Cloud Platform, さくらのクラウドをはじめとしたクラウドネイティブ世代が多い昨今です。
あるいは OpenStack や CloudStack といったプライベートクラウドソリューションもインフラのエキスパートによって運用されています。
クラウド環境なら AWS を例に取ると RDS であれば勝手にデータベースのバックアップも取ってくれてその世代管理もしてくれます。
EC2 インスタンスの EBS のスナップショットも手軽に取れることでしょう。
永続化されたオブジェクトストレージであれば Amazon S3, Glacier を使ってフルマネージドなバックアップが実現できますね。
オンプレミス
オンプレ環境でのバックアップはどうしているでしょう。
いくつか選択肢として挙げますと
それぞれの使い所
アプライアンス製品
まず、札束の力をもってベンダのサポートを受けられるアプライアンス製品を使うのもいいと思います。ただし製品選びは慎重になりましょう。高い買い物ですので。
OSS
では OSS を使ったバックアップに視点を移してみましょう。
rsync
数台レベルのバックアップであれば rsync と組み合わせた LVM スナップショットや mysqldump
, pg_dump
, あるいは物理バックアップ等を使って自前で構築するのもいいと思います。
rsnapshot
10台ぐらいを超えてくるとその辺全部手動で構築するのが辛くなってきて rsync に設定ファイルベースでバックアップできる rsnapshot が手軽でいいかなという気持ちになってきます。
Bacula
50台以上ぐらいになってくると rsnapshot の設定ファイル書いた人ならわかると思いますが、これでもけっこう大変になってきて正直このままやっていけるのかなという気持ちになってきます。
これぐらいのレベルになってくると専用のバックアップサーバが欲しくなって来ます。
Bacula や Amanda 等色々ありますが、基本的にスケーラビリティのある OSS を調査し、
弊社のインフラチームで議論をした結果、 Bacula を使うことになりました。
Bacula は Linux等のUNIX系サーバならもちろんのこと、WindowsもVSS(Volume Shadow Copy Service)を使ってバックアップができます。
バックアップデバイスとしてテープが主流の時代から存在する伝統的なバックアップソリューションとなります。
設定ファイルが独特ですが、フルバックアップ、増分バックアップ、差分バックアップ等一通りの必要な機能を備えています。
スケーラビリティを考慮して Bacula は複数のモジュールからなります。
ことのあらまし
弊社での運用はHDDを使ったディスクベースのバックアップシステムを構築しています。
最初はカタログデータベース(MySQL)含め全部のモジュールを一つのサーバに突っ込んでスモールスタートな運用で始めました。
徐々にバックアップジョブが増えていき、ジョブ数が100台以上になり、管理するファイル数が6千万件、容量にして3TBぐらいまで増えてきて全体のバックアップ時間も長くなりました。
容量的にまだ余裕はありましたが、ディスク I/O が追いつかず(主に MariaDB )ついには Storage Daemon がエラーを吐く始末でした。
いよいよBaculaを1ノード構成からスケールアウトさせる必要がありと判断し、スケールアウトミッションが開始されました。
バックアップに関するナレッジが少ない、もっというとスケールアウトする日本語のナレッジが少ないなということで、少しでもBaculaの利用が広まればと思い、本稿を書きました。
Key Terms
- Bacula Director
- Bacula Storage Daemon(SD)
- Bacula File Daemon(FD)
- MariaDB(MySQL)
- PostgreSQL
課題
- ジョブ数が多すぎて(ファイル数が多すぎて)書き込み遅延(ファイルシステム、DB)が発生する
- SDに定義されているストレージデバイスが1つのため、並列にジョブを処理できずトータルのバックアップ時間が伸びる
施策
- I/O 要求を分散できるように 2TB RAID1 の IA サーバを +4 つぐらい用意する
- RDBMS を MariaDB(MySQL) から PostgreSQL に変更する
サーバ一覧
5 つのサーバになる。
- bk1.local
- bk2.local
- bk3.local
- bk4.local
- bk5.local
バージョン
- Bacula 7.0 系
- Catalog DB: PostgreSQL 9.6
アーキテクチャの概要
データベースサーバは PostgreSQL 9.6 を使用。
bk1 は bk2-5 を NFS マウント。
I/O 要求を分散。
もっと効率的な I/O 要求を分散するのであれば iSCSI を使うのでも可。
これらを図にすると以下のような感じになります。
Bacula の用語の概念について
Volume
ボリュームは 1 つのテープに相当する。
ディスクベースのバックアップでは 1 ファイルに相当する。
これにラベリングを行う。
Pool
Volume を束ねた論理的なストレージの単位
Bacula Director 側の設定では基本的に Pool を指定することでおこなう
現在定義されているジョブ数の調査
$ sudo egrep "^Job[^\\w]" bacula-dir.conf | wc -l
112
Job の分散設計
分散する物理ストレージサーバで分散する。
ここでは 5 つ。
2 の倍数になるように調整します。
112 / 5 = 22.4 = 24
24 jobs/storage
各ジョブは Concurrency 24 多重で振り分けてその中でジョブをさらに 2 多重で書き込み要求をするようにします。
つまり各 Device あたりに 12 ジョブ
許可された Concurrency 24 を超えたものは次のストレージに書き込みを行うジョブに替える。
ホワイトペーパーからの引用では
CommunityDiskBackup.pdf より
3.2 Using “Maximum Concurrent Jobs” on Devices
Using Maximum Concurrent Jobs option is a better choice to restrict the concurrency on a given Device.
Maximum Concurrent Jobs を使うのがベターチョイスと書いてある。
実装の詳細
キモは上記概念を理解することと対応する設定を理解すること。
これが間違ってたらバックアップジョブは詰まるし、そもそも Bacula Director は起動しなかったりと、いいことは何もないです。
Storage Daemon の設定
Autochanger FileChgr1 を定義する。
その下に紐づくディスク領域を Device ディレクティブで定義する。
同じディレクトリをポイントした Device を定義します。
これは同じストレージに対して並列で書き込み要求を Job が行えるようにするためのテクニックです。
Autochanger はテープの概念らしいのですが、正直テープのことはわからんぽです。。。
仮想的なオートチェンジャーを定義します。
RestoreStorage1 はそのストレージ(ディスクデバイス) FileChgr1 に関してリストアのためのスロットを用意しておくためのものです。
他のジョブで使用されないように AutoSelect = no
としています。
他のジョブが実行中でもリストアは並行して実行できるようになるというものです。
CommunityDiskBackupDesign.pdf
3.5 Running Restores and Backups Concurrently
Notice that we have set AutoSelect = no in the Device definition. This prevents
Bacula from selecting that particular device for backup. When AutoSelect=no is
present, the only way to use that Device is by explicitly referencing it (rather than
the Autochanger) in the Director.
これを並列化できるストレージの数分だけ書きます。
正直つらいことこのうえないので内部的には Python + Jinja2 テンプレートで吐き出せるようにします。
bacula-sd.conf の設定で書くとこんな感じです。
Drive Index
は 0 始まりでかぶらないようにします。
{% for i in changer_seq %}
{% set idx = i+1 %}
#
# Define a Virtual autochanger
#
Autochanger {
Name = FileChgr{{ idx }}
Device = FileChgr{{ idx }}-Dev1, FileChgr{{ idx }}-Dev2, RestoreStorage{{ idx }}
Changer Command = /dev/null
Changer Device = /dev/null
}
Device {
Name = FileChgr{{ idx }}-Dev1
Device Type = File
Media Type = File{{ idx }}
Archive Device = /var/bacula/backup{{ idx }}
Random Access = Yes;
AutomaticMount = yes;
RemovableMedia = no;
AlwaysOpen = no;
Autochanger = yes
Drive Index = 0
Maximum Concurrent Jobs = {{ concurrent_jobs_per_device }}
Volume Poll Interval = 15
Label Media = yes;
}
Device {
Name = FileChgr{{ idx }}-Dev2
Device Type = File
Media Type = File{{ idx }}
Archive Device = /var/bacula/backup{{ idx }}
Random Access = Yes;
AutomaticMount = yes;
RemovableMedia = no;
AlwaysOpen = no;
Autochanger = yes
Drive Index = 1
Maximum Concurrent Jobs = {{ concurrent_jobs_per_device }}
Volume Poll Interval = 15
Label Media = yes;
}
Device {
Name = RestoreStorage{{ idx }}
Device Type = File
Media Type = File{{ idx }}
Archive Device = /var/bacula/backup{{ idx }}
Random Access = Yes;
AutomaticMount = yes;
RemovableMedia = no;
AlwaysOpen = no;
Autochanger = yes
Drive Index = 2
AutoSelect = no
Maximum Concurrent Jobs = 5
Volume Poll Interval = 15
Label Media = yes;
}
##################################################################
{% endfor %}
Python の Jinja2 テンプレートで読み込むファイルの例
#!/usr/bin/python
# -*- coding: utf-8 -*-
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
total_jobs = 113
num_storages = 5
concurrency_per_jobs = total_jobs / 5 + 2
storage_concurrency = 2
concurrent_jobs_per_device = concurrency_per_jobs / storage_concurrency
tpl_params = {
'concurrency_per_storage':concurrency_per_jobs,
'changer_seq':xrange(num_storages),
'concurrent_jobs_per_device': concurrent_jobs_per_device
}
f = open('bacula-dir.conf', 'w')
tpl = env.get_template('bacula-dir.conf.j2')
html = tpl.render(tpl_params)
f.write(html.encode('utf-8'))
f.close()
f = open('bacula-sd.conf', 'w')
tpl = env.get_template('bacula-sd.conf.j2')
html = tpl.render(tpl_params)
f.write(html.encode('utf-8'))
f.close()
やってはいけない
AutoChanger.Device.Archive Device のストレージが異なる
英語のドキュメントを読み間違って、ある AutoChanger にぶら下がる Device の Archive Device のポイントするディレクトリを別にしてしまうこと。
Media Type は AutoChanger ごとに別にしなければならない。
AutoChanger に紐づく Device で共通の Archive Device をポイントする必要がある。
一見するとうまくいきそうに見えますが、じゃないと 1 発目のバックアップで早晩ジョブがデッドロックを起こして詰まります。
CommunityDiskBackup.pdf
2.2 Using a Virtual Autochanger
Each Device should point to the same directory, have the same MediaType, and
must have a different Drive Index. That means also that if two Devices point to
different directories, they should have different MediaType.
Setting Maximum Concurrent Jobs will limit the number of jobs that will be able
to use this Device at the same time. By default, when a Device is full, Bacula will
use automatically the next Device available.
Bacula Director の設定
分散するストレージサーバの数だけ下記ブロックに書きます。
つまり File1
から File5
までできます。
File1
<=>FileChgr1
RestoreStorage1
<=>FileChgr1
のような対応として Storage リソースディレクティブを記述します。
File1 はバックアップのジョブ、
RestoreStorage1 はリストアジョブ用のストレージ定義となります。
Address が bk1.local 固定なのは Storage Daemon は 1 つで、そのバックエンドは NFS としてマウントして仮想的な一つの巨大なストレージに見せているのでこういった書き方になっています。
bacula-dir.conf
{% for i in changer_seq %}
{% set idx = i+1 %}
Storage {
Name = File{{ idx }}
Address = bk1.local
SDPort = 9103
Password = "<password>"
Device = FileChgr{{ idx }}
Media Type = File{{ idx }}
Maximum Concurrent Jobs = {{ concurrency_per_storage }}
Autochanger = yes
}
{% endfor %}
{% for i in changer_seq %}
{% set idx = i+1 %}
Storage {
Name = RestoreStorage{{ idx }}
Address = bk1.local
SDPort = 9103
Password = "<password>"
Device = RestoreStorage{{ idx }}
Media Type = File{{ idx }}
Maximum Concurrent Jobs = {{ concurrency_per_storage }}
Autochanger = yes
}
{% endfor %}
PostgreSQL のチューニング
いろいろとやっていると MariaDB(MySQL) だとチューニングが厳しくなってきて PostgreSQL に RDBMS を変更するということが多いです。
別に MariaDB(MySQL) を dis っているわけではなく、適材適所だということです。
SEQUENCE のキャッシュをチューニングします。
If you despool attributes for many jobs at the same time, you can tune the sequence object for the FileId field.
$ sudo -u postgres psql bacula
psql (9.6.6)
Type "help" for help.
bacula=# ALTER SEQUENCE file_fileid_seq CACHE 1000;
ALTER SEQUENCE
根幹的なチューニング
とにかく file テーブルのレコード数がやばい。
ジョブ数と扱うファイル数によるが、数千万件のオーダー行く。
bacula=# select count(*) from file;
count
----------
61670899
(1 row)
搭載するメモリの許す限り、 shared_buffers
を割り当てます。
32GB の搭載メモリなのでかなりギリギリの 50% ぐらいを攻める。
postgresql.conf
shared_buffers = 16GB
ジョブの定義の仕方
ようやくここまでで I/O 負荷を分散するお膳立てが完了しました。
最後にそれぞれのバックアップジョブをどの Bacula Director に定義した Storage リソースを使用するのかを設定していきます。
File1 から File5 まで均等に。
仮想化ホストの運用までしている場合はその Bacula ジョブが同一仮想ホストの
仮想マシン上で多重実行されることによってホスト OS が過負荷になってしまわないように注意して分散配置しなければなりません。
Job {
Name = "Backup-awesome-server"
JobDefs = "DefaultJob"
Storage = File5
Client=awesome-server-fd
Schedule = "WeeklyCycle-tue"
}
運用状況の確認
あとは 1 週間ぐらいバックアップ状況を観察します。
1日乗り切ったぐらいで安心してはいけません。
いくつかリストアしてみてちゃんとバックアップできているのか検証もしてみましょう。
ディスク使用量の確認
[fred@bk1 ~]$ sudo df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/root 11T 4.1T 6.9T 38% /
devtmpfs 16G 0 16G 0% /dev
tmpfs 16G 4.0K 16G 1% /dev/shm
tmpfs 16G 58M 16G 1% /run
tmpfs 16G 0 16G 0% /sys/fs/cgroup
/dev/mapper/centos-home 50G 663M 50G 2% /home
/dev/sda2 497M 282M 216M 57% /boot
172.16.0.2:/var/bacula/nfs 1.6T 953G 657G 60% /var/bacula/backup2
172.16.0.3:/var/bacula/nfs 1.8T 769G 1.1T 43% /var/bacula/backup3
172.16.0.4:/var/bacula/nfs 1.8T 412G 1.4T 23% /var/bacula/backup4
172.16.0.5:/var/bacula/nfs 1.8T 430G 1.4T 24% /var/bacula/backup5
tmpfs 3.2G 0 3.2G 0% /run/user/1000
Volume ファイルの分散
それぞれのマウントポイントにどのようにファイルが配置されているか
[fred@bk1 ~]$ sudo ls -lh /var/bacula/backup{1..5}
/var/bacula/backup1:
total 406G
-rw-r----- 1 bacula tape 125G Nov 25 01:08 Full-0003
-rw-r----- 1 bacula tape 14G Nov 25 02:30 Full-0006
-rw-r----- 1 bacula tape 8.8G Nov 25 02:37 Full-0012
-rw-r----- 1 bacula tape 11G Nov 25 04:32 Full-0017
-rw-r----- 1 bacula tape 181G Nov 26 11:49 Full-0022
-rw-r----- 1 bacula tape 4.9G Nov 27 02:52 Full-0042
-rw-r----- 1 bacula tape 31G Nov 26 01:18 Inc-0024
-rw-r----- 1 bacula tape 129M Nov 26 02:12 Inc-0031
-rw-r----- 1 bacula tape 2.3G Nov 27 01:20 Inc-0039
-rw-r----- 1 bacula tape 30G Nov 27 04:16 Inc-0045
/var/bacula/backup2:
total 465G
-rw-r----- 1 bacula tape 121G Nov 25 07:25 Full-0004
-rw-r----- 1 bacula tape 146G Nov 25 06:31 Full-0009
-rw-r----- 1 bacula tape 62G Nov 26 02:36 Full-0013
-rw-r----- 1 bacula tape 1.9G Nov 25 06:32 Full-0026
-rw-r----- 1 bacula tape 87G Nov 27 03:17 Full-0036
-rw-r----- 1 bacula tape 38G Nov 26 01:50 Inc-0027
-rw-r----- 1 bacula tape 2.5G Nov 27 00:03 Inc-0033
-rw-r----- 1 bacula tape 7.3G Nov 27 01:59 Inc-0041
-rw-r----- 1 bacula tape 1.9G Nov 27 02:15 Inc-0047
/var/bacula/backup3:
total 667G
-rw-r----- 1 bacula tape 3.0G Nov 25 08:53 Catalog-0028
-rw-r----- 1 bacula tape 5.1G Nov 26 14:35 Catalog-0038
-rw-r----- 1 bacula tape 5.3G Nov 27 05:38 Catalog-0053
-rw-r----- 1 bacula tape 50G Nov 25 04:26 Full-0005
-rw-r----- 1 bacula tape 15G Nov 25 02:49 Full-0010
-rw-r----- 1 bacula tape 16G Nov 25 04:43 Full-0016
-rw-r----- 1 bacula tape 260G Nov 26 01:55 Full-0019
-rw-r----- 1 bacula tape 291G Nov 27 03:48 Full-0037
-rw-r----- 1 bacula tape 7.5G Nov 26 01:18 Inc-0029
-rw-r----- 1 bacula tape 12G Nov 27 01:10 Inc-0034
-rw-r----- 1 bacula tape 795M Nov 27 02:10 Inc-0043
-rw-r----- 1 bacula tape 3.8G Nov 27 05:02 Inc-0051
/var/bacula/backup4:
total 341G
-rw-r----- 1 bacula tape 50G Nov 25 01:48 Full-0001
-rw-r----- 1 bacula tape 15G Nov 25 02:53 Full-0008
-rw-r----- 1 bacula tape 91G Nov 25 06:00 Full-0014
-rw-r----- 1 bacula tape 42G Nov 25 05:07 Full-0018
-rw-r----- 1 bacula tape 58G Nov 26 05:45 Full-0021
-rw-r----- 1 bacula tape 2.7G Nov 27 02:15 Full-0050
-rw-r----- 1 bacula tape 41G Nov 26 01:14 Inc-0025
-rw-r----- 1 bacula tape 378M Nov 26 02:13 Inc-0032
-rw-r----- 1 bacula tape 1.4G Nov 27 01:32 Inc-0040
-rw-r----- 1 bacula tape 927M Nov 27 02:12 Inc-0046
-rw-r----- 1 bacula tape 41G Nov 27 05:31 Inc-0052
/var/bacula/backup5:
total 155G
-rw-r----- 1 bacula tape 22G Nov 25 01:40 Full-0002
-rw-r----- 1 bacula tape 18G Nov 25 04:18 Full-0007
-rw-r----- 1 bacula tape 63G Nov 25 03:52 Full-0011
-rw-r----- 1 bacula tape 6.7G Nov 25 04:02 Full-0015
-rw-r----- 1 bacula tape 20G Nov 27 02:18 Full-0020
-rw-r----- 1 bacula tape 19G Nov 27 03:07 Full-0048
-rw-r----- 1 bacula tape 5.4G Nov 26 01:32 Inc-0023
-rw-r----- 1 bacula tape 1008M Nov 26 02:19 Inc-0030
-rw-r----- 1 bacula tape 565M Nov 27 01:10 Inc-0035
-rw-r----- 1 bacula tape 1.4G Nov 27 02:11 Inc-0044
-rw-r----- 1 bacula tape 496M Nov 27 02:17 Inc-0049
まとめ
ここまでの設定で Bacula Storage Daemon と Bacula Director はスケーラブルな実装になったはずです。
もっとスケーラブルにするのであれば Bacula Director を分けたり、 PostgreSQL Catalog DB も専用の DB サーバに分けたりするのがよいでしょう。
今のところこれが Bacula をスケールさせる手段としていい感じで動いています。
バックアップもシンプルなようで非常に奥が深いものです。
ユニキャストでは一緒に働ける仲間を募集しています。
それではみなさん、いい Ops ライフを!
失敗した方法
Storage Daemon を複数立てて I/O 負荷を分散する
最初はうまくいったように見えるが、あとでジョブが詰まるようになる。
たぶん設定の問題。
References
公式ドキュメント
ディスクベースバックアップのベストプラクティス
公式から出されているディスクベースバックアップを実装する際のベストプラクティスが記述されたホワイトペーパーです。
Typo が多分に含まれているので注意しながら読みましょう。
その他
投稿者紹介
-
* Bio: Software Engineer, Network and Server Engineer
* Certification:
IPA: FE, AP, Network Specialist
Cisco: CCNA R&S, CCNP R&S
LPI: LPIC Level1, Level2, LPIC-3 Specialty LPI-304 Virtualization &High Availability