15
13

More than 3 years have passed since last update.

macOS: TimeMachine バックアップを AWS 上に保全する

Last updated at Posted at 2019-09-29

将来、家に隕石が落ちてきた時の最後の砦として、自分の PC にある日々の運用データ一式をクラウド上に保全して枕を高くして寝たい。また、超強力な太陽嵐が地球上に降り注いでも、データの整合性は可能な限り担保したい。

そんな夢を見たのでどこまで実現可能かを考えてみた。思考実験以上、運用実験未満。

現況

今のところ(高速回線環境がないので)最終的なバックアップ完了までこぎ着けていない。よって復元も試せていない。試行レポートをお待ちしています。

ゴール

AWS 上に macOS の TimeMachine 機能を使ってデータを保管したい。また、手元の Mac のディスクが壊れた時のリカバリー元としても使えるようにしたい。可能な限り高い信頼性を確保し、できるだけセキュアに。

要件

ネットワーク

  • 通信経路をセキュアに保つこと
  • macOS の標準機能だけで VPN 接続を確立できること

データ

  • データは自分で暗号化・復号できること
  • 信頼性の高い(ビット反転等に強い)ディスク環境を構築すること
  • 可能な限り高い冗長性を確保できる(コントロールできる)こと
  • バックアップだけでなく、リカバリモードで起動した Mac から OS の復元ができること

方針

  • 特別な機器(ルータ等)を利用せず、ソフトウェアのみで実現する。
  • 信頼性の高いツールを組み合わせて実現する。がんばりすぎない。
  • サーバは1インスタンスで済ませる。

ソリューション

検討した結果、ソリューションとして以下のソフトウェアを組み合わせることとした。

VPN 環境: Algo

Algo は、クラウドサービス上に IPSec (最近では WireGuard も可)を使った VPN インスタンスを簡単に構築できるツール。PC の設定ファイルも生成してくれるので便利。実行すると EC2 のインスタンスを立ててネットワークの設定まですべて行ってくれる。

OS は Ubuntu の最新版がインストールされる。

ファイルシステム: ZFS

みんな大好き ZFS には同一データの複数コピーを保持する機能がある。複数のコピーがあると、ビット反転(腐敗)などが起きた時にメンテナンスコマンドにより整合性を回復できる。

またソフトウェア的に RAID を組める機能を利用し、ディスクのハードウェアエラーや操作ミス等もカバーする。

データ暗号化: LUKS によるディスク暗号化

ディスクの暗号化機能は ZFS にも搭載されているが、Linux 上での安定性や可用性を考えて今回はこちらとした。

(他の方法との比較についてはのちほど論じる)

ディスクデバイス: Amazon EBS

ディスク系のソリューションとなると EFS も候補に挙がるが、(AWS に任せず)自前で暗号化したいとなるとブロックデバイスである EBS 一択となる。ただし、コストパフォーマンスを高めるためにタイプは低速だが低コストの Cold HDD (sc1) を指定する。ネット越しのバックアップはネットワーク速度がボトルネックとなるのでディスク自体は低速でもまったく構わない。

また、必要に応じて EBS ボリュームのスナップショットを定期的に取得してもよいだろう(ただし費用はけっこうかかる)。

通信プロトコル: netatalk

TimeMachine は AFP プロトコルで動作するので netatalk サーバを稼働させる。なお現代では SMB (CIFS) でも可能なので samba を利用してもよさそうだ1

Algo のセットアップ

Algo のセットアップは詳しい記事がネットにたくさんあるので省略。いくつかポイントだけ。

iptables の設定を追加

netatalk のためにポートを開放する。任意のプロトコルを通す設定を公式に追加する方法がわからないので、次のようなパッチを当ててごまかすこととした。

vi iptables.patch
--- roles/common/templates/rules.v4.j2.orig 2020-06-14 13:10:38.000000000 +0900
+++ roles/common/templates/rules.v4.j2  2020-06-14 13:11:21.000000000 +0900
@@ -81,6 +81,10 @@
 # Accept DNS traffic to the local DNS resolver
 -A INPUT -d {{ local_service_ip }} -p udp --dport 53 -j ACCEPT

+# Netatalk
+-A INPUT -p tcp --dport 548 -m conntrack --ctstate NEW -j ACCEPT
+-A INPUT -p udp --dport 5353 -j ACCEPT
+
 # Drop traffic between VPN clients
 -A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }}
 # Drop traffic to VPN clients from SSH tunnels
patch < iptables.patch

設定ファイルの書き換え

config.cfg を編集する。

IAM 作成と権限設定

専用の IAM を作成しておくとよい。インストラクションにあるように、権限の欄に指定された JSON を記述すること。

Python

python3 が必要。macOS には付属しないので brew 等で用意する。詳しくは algo の README を参照。3.8 ではエラーが出たので 3.7 推奨。

ユーザリスト

デフォルトで users が3つ指定されているが1つあればいいので、適当に減らしたりリネームする。admin とでもしておく。

インスタンスタイプ

2019 年は t2.nano を指定できたが、2020 年はメモリ 512 MB の環境では cryptsetup がメモリ不足でディスクの復号に失敗するようになっているので注意。安いインスタンスで動かせていたのに残念だ。t2.micro 以上にしておこう。ちなみに swap 領域を用意してもダメだった。

(なお、t3 ファミリーでの起動も試したが現時点ではうまくいかなかった。おそらく ENA 周りが関係するのだろう)

生成されたデータのバックアップ

algo コマンドによるセットアップ後に、configs 以下に秘密鍵や諸々のファイルが生成される。これらを別マシン/メディアへ安全に保全すること。もし可能ならバイト列を石版に刻んで対爆仕様の金庫に放り込み、世界中に分散設置する。衛星に乗せるのもいいだろう。

インスタンスの立ち上げ

実際に algo を実行しインスタンスを作成する。日本在住者なら ap-northeast リージョンを選択するとよい。

./algo

AWS コンソールから、自動作成された EC2 インスタンスの削除保護を有効にし、シャットダウン動作を「停止」に変更しておこう。

無事に作成されたら ssh でログインする。

ssh -i configs/algo.pem ubuntu@xx.xx.xx.xx
sudo su -

ネットワークとマシンの準備

最新版にアップグレードしておく。

apt-get update
apt-get upgrade -y

必要なライブラリを入れる。

apt install -y cryptsetup zfsutils-linux netatalk

ディスク領域の準備

方針

ZFS の機能を使って RAID を組む。

可能なら raidz2 オプションを採用し RAID6 を組みたいが、EBS (Cold HDD (sc1)) を4台も使うと最低でも $60/month がかかる。ディスク構成はあとから変更できるので、まずは2台で構築してみる。

なお EBS 自体の暗号化機能は使用しない。

セットアップ

ディスク設計

今回は EBS ボリュームごとに ZFS のストレージプールを作成し、それらを raid として束ねることとした。

EBS ボリュームを用意する

EBS は EC2 インスタンスと同じアベイラビリティゾーンに作成すること。AWS コンソール上から EC2 インスタンスにアタッチする。

必要な数だけ作成する。ひとまず、タイプは sc1 で 500GB ものを2つ作り、それぞれタグの Name として algo disk1algo disk2 などとセットしておくとよいだろう。今回は EBS 暗号化は使わない。

通常なら /dev/sdf にアタッチされ、インスタンスからは /dev/xvdf として見えるはずだ。
2つめ以降は /dev/xvdg, /dev/xvdh ... などと振られる。

ls -lF /dev/xvd[f,g,h,i]

暗号化ボリュームをセットアップする

次のようにして暗号化ボリュームをセットアップする。もちろん既存のファイルは全消去されるので注意すること。パラノイアなひとはすべて違うパスフレーズにするのも良いかもしれない。

cryptsetup --verbose --verify-passphrase luksFormat --cipher aes-cbc-essiv:sha256 --key-size 256 /dev/xvdf
cryptsetup --verbose --verify-passphrase luksFormat --cipher aes-cbc-essiv:sha256 --key-size 256 /dev/xvdg
...

それぞれ tmdisk1, tmdisk2 ... としてマウントする(名前は任意)。

cryptsetup luksOpen /dev/xvdf tmdisk1
cryptsetup luksOpen /dev/xvdg tmdisk2
...

ZFS プールに登録する

ここでは2台なので mirror を指定する。3台なら raidz1、4台なら raidz2 ... というように設定できる。

zpool create -o ashift=12 -O normalization=formD tmdisk mirror \
    /dev/mapper/tmdisk1 \
    /dev/mapper/tmdisk2

状態を確認する。

zpool list
zpool status
NAME     SIZE  ALLOC   FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
tmdisk  1008M   408K  1008M         -     0%     0%  1.00x  ONLINE  -
  pool: tmdisk
 state: ONLINE
  scan: none requested
config:

    NAME         STATE     READ WRITE CKSUM
    tmdisk       ONLINE       0     0     0
      mirror-0   ONLINE       0     0     0
        tmdisk1  ONLINE       0     0     0
        tmdisk2  ONLINE       0     0     0

ファイルシステムオプション

いくつか有用なオプションを付けておく。

zfs set compression=on tmdisk    # ディスク圧縮
zfs set copies=2 tmdisk    # 複数のコピーを保持

ここでは copies=2 を設定している。もちろんそれだけ容量(と書き込み時間)を消費するので注意すること。ちなみに指定できる最大値は 3 となっている。

以下はお好みで。

zfs set atime=off tmdisk    # noatime 相当(ディスクのアクセス速度を上げたい)
zfs set relatime=on tmdisk    # relatime 相当(バランスがよさそうなので)
zfs set exec=off tmdisk    # noexec 相当(バイナリの実行を禁止。安全性を上げたい)

現在のオプション値を確認することもできる。

zfs get all tmdisk

なお、冗長性を確保するという目的のため、当然ながら 重複除去機能 は使用しない2

書き込みテスト

ここでは 1GB のディスクで copies=2 を指定しているので、圧縮が効かない場合にはせいぜい 500MB 程度しか書き込めないはずである。確かめる。

head -c 600m /dev/urandom > /tmdisk/data
head: error writing 'standard output': No space left on device
ls -lh /tmdisk/data
rm -f /tmdisk/data

これで secure かつ robust な /tmdisk を準備できた。

ユーザ

netatalk へ接続するための Unix 実ユーザアカウントを適当に作成する。ここでは tmuser とした。

useradd -d /home/tmuser -g backup -m -s /usr/sbin/nologin -u 340 tmuser
passwd tmuser    # これが macOS からアクセスするときのパスワードとなる

ディスクの所有権を調整しておく。

chown -R tmuser:backup /tmdisk
chmod 750 /tmdisk    # あるいは 700 か

netatalk

/tmdisk ボリュームを netatalk に登録する。TMDisk の名前は任意。macOS 上で見えるディスク名となる。

vi /etc/netatalk/AppleVolumes.default
/tmdisk "TMDisk" allow:tmuser cnidscheme:dbd options:usedots,upriv,tm

もしユーザ名を変えた場合は tmuser の部分を変更するのを忘れないように。また、デフォルトとして有効になっている ~/ の行が必要なければオフにしてもよいだろう。

vi /etc/netatalk/afpd.conf
# for TimeMachine
- -tcp -noddp -uamlist uams_dhx.so,uams_dhx2_passwd.so -nosavepassword

もし afpd のログを出したければ次も追記するとよい。

- -setuplog "default log_debug /var/log/afpd.log"

追記:Ubuntu 20.04 以降だと次のようにする必要があるようだ。

vi /etc/netatalk/afp.conf
[TMDisk]
path = /tmdisk
time machine = yes
force user = tmuser

起動する。

systemctl restart netatalk
lsof -i:548
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
afpd    7239 root    4u  IPv4  39717      0t0  TCP *:afpovertcp (LISTEN)

接続の準備

hosts ファイル

利便性向上のため、手元の macOS の /etc/hosts に例えば次のように書いておくとよい。

sudo vi /etc/hosts
10.19.49.1      tmdisk-aws tmdisk-aws.local

この IP アドレスは Algo の roles/vpn/defaults/main.yml にハードコードされているのでそのまま記述してよい。

実行する

VPN

まず VPN に接続する。「ネットワーク」環境設定に Algo VPN xx.xx.xx.xx IKEv2 が追加されているはずなのでこれを選び、「接続」をクリックする。

正しく接続されているかどうか確認する。

ping tmdisk-aws.local

マウントする

Finder の「サーバへ接続 (cmd+K)」へアドレスを入れて接続する。

afp://tmuser@tmdisk-aws.local/TMDisk

試しになにかファイルを書き込んでみると次のように反映されるはずだ。

ls -l /tmdisk/
drwxr-x--- 3 root   backup     3 Sep 28 08:02 'Network Trash Folder'
drwxr-x--- 3 root   backup     3 Sep 28 08:02 'Temporary Items'
drwxr-sr-x 1 tmuser backup 59601 Sep  5 05:15  test.jpg

あとは通常通り、TimeMachine 環境設定を開いて TMDisk をバックアップ先に指定するだけだ。

UUID な .sparsebundle が作られ、そのうちマシン名に変わるはずだ。

drwx--S--- 4 tmuser backup 7 Oct  9 10:42  5097C544-06B1-58F6-9B6B-2C1F065E3F9D.sparsebundle/

運用

ディスクの冗長化

費用に余裕があれば、数 TB の EBS ボリュームを4つ用意し ZFS の raidz2 を指定して RAID6 相当を組むと万全だろう。

ただ、(物理ディスクとは違って)EC2 上の仮想ディスクが壊れる可能性は相対的にかなり低いと思われるので、ヒューマンエラーを加味した落としどころとしては、3台で raidz1 の RAID5 構成で十分だとは思う。

定期的なディスクのチェック

通常は /etc/cron.d/zfsutils-linux で定期的に zpool scrub コマンドが実行され、不整合は自動修復されるはずだ。デフォルトで月に1度だが、もう少し頻度を上げてもいいかもしれない。

EBS スナップショットの定期作成

費用に余裕があれば、1日それぞれひとつのスナップショットを撮っておくとさらに安心だろう。ただし、かなり費用がかかる富豪的手法ではある。

仮に4つの 1TB ディスクを使用し、ディスクの使用量を各 50% とし、1日にひとつ、5つのスナップショットを保持した場合、現状では $500/月程度かかるだろう。

OS 環境

細かいこといろいろ。

ホスト名

hostnamectl set-hostname tmdisk1.example.com

タイムゾーン

Algo デフォルトでは UTC なので JST にしておく。

timedatectl set-timezone Asia/Tokyo

時刻同期

ntp を起動しておく。

apt-get install -y chrony

IP 制限

可能なら、EC2 (VPC) のセキュリティグループで自宅/自社からのアクセスのみに制限しておこう。
ssh は hosts.allow へ記述する方法もある。

念のため netatalk も hosts.allow で制限したいなら次のように書ける。

afpd : 10.19.48.1 : allow
afpd : ALL : deny

運用サイクル

一度セットアップすれば次のようなフローになる。

OS 起動時

暗号化ディスクをマウントする。複数のディスクを使う場合は繰り返す。

cryptsetup luksOpen /dev/xvdf tmdisk1
cryptsetup luksOpen /dev/xvdg tmdisk2
...

ZFS 領域をマウントする。

zpool import tmdisk
zpool list -v

もしマウントされていなかったら手動で。

zfs mount tmdisk

OS シャットダウン時

ふつうにマシンを shutdown/reboot しても問題ない。あえて手順を踏むなら次のようになる。

ZFS 領域をアンマウントする。

zfs unmount tmdisk
zpool export tmdisk
test -e /tmdisk && rmdir /tmdisk    # 次回マウント時に失敗するので消しておく

ディスクの暗号化を解除する。

cryptsetup luksClose /dev/mapper/tmdisk1
cryptsetup luksClose /dev/mapper/tmdisk2
...

課題

実効容量を正しく認識してくれるか

ZFS の圧縮や複数コピーオプションを有効にしている関係上、実効領域が伸び縮みして見えている実ディスク容量と合わない(と思われる)。この差違を TimeMachine が正しく認識して消し込みを行ってくれるか不明。心配なら次のような対処法があり得るか。

  • netatalk の設定で TimeMachine 領域の容量を明示的に指定する (volsizelimit:512000)
  • ZFS 上で quota をかける (zfs set quota=500G tmdisk)

可能であれば、使用したい容量の 2.5 倍以上を確保しておくと安心かもしれない。

ファイル名の大文字小文字の区別問題

macOS 自体は case-insensitive なので、ZFS 上もそれに合わせておくほうが安全かもしれない。また、SMB を使う際にはまた話が変わってくるようだ

zfs set casesensitivity=caseinsensitive tmdisk

ファイル名の UTF8 正規化問題

macOS のファイル名正規化は NFD なので、ZFS create 時に -O normalization=formD などと指定できるようだ。macOS に準拠させたいならどうすべきか。あるいは netatalk が差違を吸収してくれると期待してよいものか。

クラウドからの OS リカバリは成功するのか

理論上は成功しそうだが、試せていない。実効的な速度が出るものなのかも不明。

回線速度

うちの ADSL 12M という化石のような回線だと、計算上、初回のフルバックアップに40日ほどかかるのが難点といえば難点だ。少なくとも初回実行時は高速かつ従量課金されないネットワーク環境を用意したい。

発展

複数のサーバで別の TimeMachine バックアップを運用する

マルチ AZ 化やマルチリージョン化を試すと冗長性が高まってより安心か。

他のクラウドサービス上でも構築する

もし Google Cloud でも同等のシステムを構築できれば、マルチクラウド化して一段レベルの高い冗長性を確保できそうだ。

コールドストレージとして

定期バックアップするときだけサーバを立ち上げればセキュリティ上も安心な上に費用も安く上がる。例えば週に一度だけ AWS Lambda 等で自動でインスタンスが立ち上がるようにしておく手もありそうだが、結局 cryptsetup へパスフレーズを渡さねばならない問題は残る(AWS KMS 等を活用できるかもしれない)。

検討事項

どの段階でデータを暗号化するか

今回は LUKS を用いたが、いくつかの方針が考えられる。

TimeMachine に暗号化させる

そもそも TimeMachine に「バックアップを暗号化」するオプションがあるのでこれを使用する。ただし、サーバ上で実ファイルが見られなくなってしまうのが難点だが、お手軽確実ではある。

また、ディスクをマウントする時にパスフレーズを打たなくて済むので、サーバの自動起動・シャットダウンをプログラマブルにコントロールできるのは魅力だ。

LUKS で暗号化する

今回説明した方法。サーバ上から実ファイルが参照できるので、便利な場面は多いだろう。反面、サーバを稼働中にクラックされた場合の秘匿性に劣る。

とはいえ将来的に(プロプライエタリ製品である) macOS が使用できない環境を想定するのであれば、妥当なソリューションだと言えるだろう。

ZFS で暗号化する

未検証。使用する弊害はなさそうなので、LUKS の代わりにこちらを使用してもよさそうだ。

EBS で暗号化する

AWS に暗号化キーを預けることになるので用途にそぐわない。他の方法に比べてとくにアドバンテージがあるとも思えないので忘れてよいだろう。

複数の手段で暗号化する

基本的に暗号の重ねがけは速度的にもセキュリティ強度的にも悪手3ではある。個人的には上記のうちどれかを単体で採用すべきだと考えている。

おまけ

HTTP プロキシを立てる

VPN 接続をしているとウェブに繋がらなくなるので、一時しのぎとしてインスタンス上で Squid を動かすことにする。

apt install -y squid

/etc/squid/squid.conf へ追記する。

acl localnet src 10.19.48.1
http_access allow localnet

リロードする。

systemctl reload squid

ポートを開ける。

iptables -I INPUT 12 -p tcp --dport 3128 -m conntrack --ctstate NEW -j ACCEPT

永続化したいのであれば /etc/iptables/rules.v4 へ適宜書き込むなどする。

# Squid
-A INPUT -p tcp --dport 3128 -m conntrack --ctstate NEW -j ACCEPT

ネットワーク環境設定の Algo VPN xx.xx.xx.xx IKEv2 から右下の詳細...を選び、プロキシタグで Web プロキシに tmdisk-aws.local3128 をセットする。

メモ

macOS 10.14.6 のバグ

私の手元の macOS 10.14.6 では、 macOS Mojave 10.14.6 追加アップデート を適用するまではすぐに TimeMachine がエラーになっていた。SMB 周りの修正が入っていたので関連するのかもしれない。

また、まだ私の環境では成功を確認できていない。回線が細すぎるせいか バックアップを準備中… から先へ進むのにかなり時間がかかっているため。

遅い回線ではバックアップに失敗する

ある程度以上の回線速度(例えば 50Mbps 以上?)でないと失敗する。

まとめ

IPSec による VPN 接続と netatalk、それに ZFS の機能をフルに生かしたディスク構成により、クラウド事業者にメモリを直接覗かれない限り安全に TimeMachine バックアップを可逆的にクラウド上に置くことができた(理論上は)。


  1. Apple はむしろ SMB へスイッチしたがっているようだ。 

  2. また、有効にするととんでもなくメモリを喰うという弊害もある。 

  3. 重ねがけについては、確か「暗号技術入門」かなにかに記述があった。 

15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13