とある Bacula の大量バックアップジョブをスケールアウトさせる例

Pocket

ユニキャストの野口です。
これは ユニキャスト 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 を使ってフルマネージドなバックアップが実現できますね。

オンプレミス

オンプレ環境でのバックアップはどうしているでしょう。
いくつか選択肢として挙げますと

  1. アプライアンス製品を使う
  2. rsync
  3. rsnapshot
  4. Bacula
  5. Amanda
  6. and so on

それぞれの使い所

アプライアンス製品

まず、札束の力をもってベンダのサポートを受けられるアプライアンス製品を使うのもいいと思います。ただし製品選びは慎重になりましょう。高い買い物ですので。

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

  1. Bacula Director
  2. Bacula Storage Daemon(SD)
  3. Bacula File Daemon(FD)
  4. MariaDB(MySQL)
  5. PostgreSQL

課題

  1. ジョブ数が多すぎて(ファイル数が多すぎて)書き込み遅延(ファイルシステム、DB)が発生する
  2. SDに定義されているストレージデバイスが1つのため、並列にジョブを処理できずトータルのバックアップ時間が伸びる

施策

  1. I/O 要求を分散できるように 2TB RAID1 の IA サーバを +4 つぐらい用意する
  2. RDBMS を MariaDB(MySQL) から PostgreSQL に変更する

サーバ一覧

5 つのサーバになる。

  1. bk1.local
  2. bk2.local
  3. bk3.local
  4. bk4.local
  5. 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
  1. Installing and Configuring PostgreSQL

根幹的なチューニング

とにかく file テーブルのレコード数がやばい。
ジョブ数と扱うファイル数によるが、数千万件のオーダー行く。

bacula=# select count(*) from file;
  count
----------
 61670899
(1 row)

搭載するメモリの許す限り、 shared_buffers を割り当てます。
32GB の搭載メモリなのでかなりギリギリの 50% ぐらいを攻める。

postgresql.conf

shared_buffers = 16GB
  1. PostgreSQLのチューニング事例 – Qiita

ジョブの定義の仕方

ようやくここまでで 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

公式ドキュメント

  1. Manuals | Bacula
  2. Bacula 7.0 Main Reference
  3. Automated Disk Backup

ディスクベースバックアップのベストプラクティス

公式から出されているディスクベースバックアップを実装する際のベストプラクティスが記述されたホワイトペーパーです。
Typo が多分に含まれているので注意しながら読みましょう。

  1. Best Practices for Disk Based Backup – Bacula
  2. Disk Backup Design – Bacula

その他

  1. StorageとPoolの関係について | Bacula

投稿者紹介

Wataru Noguchi
* 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

コメントを残す

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

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください