VPSのスナップショット、バックアップ、クローンの違い
VPSのスナップショットはなぜバックアップではないのでしょうか。復元できる範囲、障害時の違い、クローン起動前に直すIDや設定を整理します。
スナップショット、バックアップ、クローンの実際の違い
VPS のスナップショットは、プロバイダーが自身のインフラ上で、利用者のアカウント内に保持するサーバーのディスクイメージです。バックアップは、元のデータを保持していたプロバイダーの支援なしに別の場所へ復元できる、データの独立したコピーです。クローンはスナップショットからデプロイした新しいインスタンスです。そのため、元のインスタンスと ID を含めて完全に同じ状態で起動します。
これらは異なる問題を解決します。スナップショットを使うと、失敗したアップグレードを数分で元に戻せます。ただし、アカウントが閉鎖されると利用できません。バックアップはプロバイダーがサービスを停止しても残ります。ただし、先にマシンを再構築するため、復元には時間がかかります。クローンを使うと、実行中の 2 台目のサーバーを 1 回の操作で用意できます。ただし、同じマシンだと認識するサーバーが 2 台存在することにもなります。
VPS スナップショットがバックアップではない理由
問題はイメージの品質ではなく、障害ドメインです。スナップショットはプロバイダーのストレージプラットフォーム上にあり、通常は元のサーバーと同じリージョンに保存されます。また、必ず同じアカウント内にあります。1 つの障害で、サーバーとスナップショットが同時に失われる可能性があります。
- アカウントが停止される、支払いに失敗する、または誰かにログイン情報を盗まれる。
- API アクセス権を持つユーザーやスクリプトがインスタンスを削除する。多くのプロバイダーでは、インスタンスを削除するとスナップショットも一緒に削除されます。そうでないと判断する前に、プロバイダーが公開している仕様を確認してください。
- リージョン全体で障害が発生し、その中のすべてに同時にアクセスできなくなる。
- サーバー上で root として実行されている何かが、
/rootに残したプロバイダー API トークンを見つけ、ディスクに触れる前にスナップショットを削除する。
バックアップとは、この 4 つすべてを乗り越えて残るコピーです。確認方法は 1 つの質問に集約できます。今日の午後にプロバイダーのアカウント自体がなくなった場合、何を復元でき、どこに復元しますか。この質問に答えられないものは、ロールバック用のツールです。最も速く復元できるため、スナップショットの取得は続けてください。そのうえで、プロバイダーが管理できないストレージにも 2 つ目のコピーを保存します。
昔からの原則は今も変わりません。データを 3 つのコピーにし、2 種類のストレージに保存し、そのうち 1 つをプラットフォーム外に置きます。プロバイダーのスナップショットと、別のインフラストラクチャ上にある restic のバックアップリポジトリを組み合わせれば、2 つの構成要素でこの原則を満たせます。
実行中のデータベースのスナップショットを復元すると壊れる理由
プロバイダーのスナップショットは、ある瞬間のブロックデバイスをそのままコピーします。アプリケーションを先に停止させることはありません。また、ページキャッシュに残っているデータを確認することもできません。そのため、イメージは少なくともクラッシュ整合性の状態になります。電源ケーブルを突然抜いた直後のディスクとまったく同じ状態です。
スタックの大部分はこの状態に対応できます。ext4 と XFS はマウント時にジャーナルを再生するため、ファイルシステムは起動します。PostgreSQL は起動時に write-ahead log を再生します。ログにもそのことが記録されます。
LOG: database system was not properly shut down; automatic recovery in progressInnoDB も同様に処理し、起動時にクラッシュリカバリのログを出力します。このリカバリは、データベースが設計どおりに動作している結果です。そのため、負荷のない PostgreSQL または MySQL の単一ボリュームスナップショットなら、通常は問題なく復元できます。
クラッシュ整合性だけでは不十分なケースもあります。そうしたケースでは問題が深刻になります。データが 2 つのボリュームに分かれている場合、root ディスクと別のデータディスクは異なる時点でスナップショットされます。そのため、データファイルとログディレクトリの内容が一致せず、リカバリで正しく再生できるログがなくなります。アプリケーションが fsync を呼び出さずに書き込むファイルは、途中まで受信したアップロードやキューファイルなどを含め、切り詰められた状態で復元される可能性があります。アプリケーションがメモリ上に保持し、タイマーでフラッシュするデータは、イメージに含まれません。
そのため、スナップショットを取得する前にダンプをディスクへ書き出してください。そうすれば、稼働中のデータファイルがどの状態でも、内部整合性が確保されたファイルを 1 つイメージに含められます。
sudo -u postgres pg_dumpall --clean --file=/var/backups/pg-$(date +%F).sql
sudo mysqldump --single-transaction --routines --all-databases > /var/backups/mysql-$(date +%F).sql--single-transaction は、書き込みをブロックせずに InnoDB テーブルの整合性のあるダンプを取得します。ダンプは 1 つの repeatable-read トランザクション内で実行されるためです。MyISAM テーブルは対象外です。MyISAM にはロックまたはサーバーの停止が必要です。ダンプを信頼する前に、空でないことと途中で切り詰められていないことを確認してください。完全な mysqldump では、tail -n 1 /var/backups/mysql-$(date +%F).sql の末尾が Dump completed コメントになります。
別のデータボリュームを使用している場合は、スナップショットに必要な数秒間だけ、そのボリュームをフリーズできます。
sudo fsfreeze -f /srv
# take the snapshot from your provider's panel or API
sudo fsfreeze -u /srvデータボリュームだけをフリーズしてください。/ は絶対にフリーズしないでください。root ファイルシステムをフリーズすると、入力に使用しているシェルを含め、サーバー上のすべての書き込みが停止します。その結果、自分自身を締め出した状態になり、ハードリセットを待つことになります。
オフサイト側: restic または Borg
スナップショットは高速側です。オフサイトコピーは、プロバイダーに障害が発生しても残る側です。restic は、重複排除、クライアント側での暗号化、S3 互換オブジェクトストレージ、SFTP、または通常のディレクトリへの書き込みに対応しているため、適した標準選択肢です。オフサイトの保存先としての storage VPS はここで有効です。バックアップリポジトリには IOPS よりも容量が必要だからです。
sudo apt update && sudo apt install -y restic
sudo sh -c 'umask 077; head -c 24 /dev/urandom | base64 > /root/.restic-pass'
sudo chmod 600 /root/.restic-pass
sudo cat /root/.restic-passそのパスフレーズを、今すぐ password manager にコピーしてください。このサーバーとは別のデバイスに保存します。restic のリポジトリは、そのパスフレーズなしでは開けず、復旧手段もありません。失ったサーバー上にパスワードのコピーが 1 つしかなかった場合、バックアップは暗号化されたデータのままです。
export RESTIC_REPOSITORY="s3:https://s3.example.com/vps-backups"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
sudo -E restic init
sudo -E restic backup /etc /srv /var/backups
sudo -E restic snapshotssudo -E はこれらの変数を保持します。これがないと root は初期状態の環境で実行され、restic はリポジトリの場所が指定されていないと報告します。restic snapshots には、実行したばかりのバックアップがホストとパスを含めて一覧表示されるはずです。リポジトリ自体も定期的に検証し、構造だけを確認せず、データの一部を読み戻してください。
sudo -E restic check --read-data-subset=5%
sudo -E restic restore latest --target /tmp/restore-check
sudo -E restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneテストしていないバックアップは推測にすぎません。少なくとも 1 回は別の VPS に復元して時間を測定し、その時間を記録してください。その数値が実際の復旧目標になるためです。Borg も堅実な選択肢です。オブジェクトストレージではなく SSH 経由でリポジトリを保存します。トレードオフについては restic と BorgBackup の比較 で説明しています。
クローンした VPS を本番環境に接続する前に修正する項目
クローンは完全な複製です。これが利点であり、問題でもあります。元のサーバーを固有にしていたものがすべて複製され、重複による衝突が発生します。
SSH host key を再生成します。 クローンには元のサーバーの /etc/ssh/ssh_host_* ファイルが含まれているため、2 台のサーバーが同じ host identity を提示します。一方を制御できる攻撃者は、その key を受け入れたすべてのクライアントに対して、もう一方のサーバーになりすませます。SSH は警告を表示しません。クライアントが想定していた key と同じだからです。
sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -A
sudo systemctl restart ssh
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pubssh-keygen -A は、daemon が必要とするすべての種類の新しい key を書き込みます。最後のコマンドで表示された fingerprint が、元のサーバーのものと異なることを確認します。sshd を再起動しても確立済みの接続は閉じられないため、現在のセッションは維持されます。クローンに誰も接続する前に実行してください。後回しにすると、継承した key をすでに信頼しているすべてのクライアントで WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! が発生し、先に ssh-keygen -R <host> を実行する必要があります。
machine ID をリセットします。 /etc/machine-id は、systemd が初回起動時に 1 回だけ生成する一意の識別子です。クローンはこの値を継承します。
sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
sudo reboot空の /etc/machine-id があると、次回の起動時に systemd が新しい値を生成します。そのため、ファイルを削除せず切り詰めます。値が重複すると、2 つの問題が発生します。DHCP でアドレスを取得するイメージでは、systemd-networkd がデフォルトで machine ID から DHCP client identifier を生成します。そのため、両方のクローンが同じ client として lease を要求し、サーバーから同じアドレスを割り当てられます。また、journald はすべてのエントリに machine ID を付けるため、集中ログ収集システムでは両方のサーバーが 1 台として記録されます。再起動後に cat /etc/machine-id を実行し、値が変わったことを確認します。
hostname を変更します。
sudo hostnamectl set-hostname web-02
grep 127.0.1.1 /etc/hostshostnamectl は /etc/hostname に書き込み、その名前を直ちに適用します。/etc/hosts は変更しないため、127.0.1.1 の行も一致するように編集します。これを省略すると新しい名前を解決できず、すべての sudo 呼び出しが失敗した名前解決を待ち、sudo: unable to resolve host web-02: Name or service not known を出力します。
イメージに組み込まれたすべての認証情報を更新します。 クローンには元のサーバーの Secret が残っているため、2 台のマシンが元のサーバーとして動作できる状態になります。SSH の authorized_keys ファイル、provider および DNS API token、アプリケーションの .env ファイル、データベースの password、TLS private key、monitoring の登録 token、restic repository password などを順に確認します。次のコマンドでその大部分を見つけられます。
sudo grep -rIlE 'PASSWORD|SECRET|TOKEN|API_KEY' /etc /srv /opt /home 2>/dev/null
sudo find / -name '.env' -not -path '/proc/*' -not -path '/sys/*' 2>/dev/nullクローンをテスト用として使用し、ネットワークトラフィックを処理しない場合は、更新ではなく revoke します。稼働中の production API token を保持する staging サーバーは、patch 適用が不十分な production サーバーと同じです。
二重実行される job を停止します。 同じ crontab を実行する 2 台のサーバーは、同じ外部システムに同じ時刻でアクセスします。
systemctl list-timers --all
sudo crontab -l
sudo ls -l /etc/cron.d /etc/cron.dailyrestic の場合は、単にエラーになるだけでなく retention が壊れるため、詳しく確認する必要があります。restic は各 snapshot に hostname を付け、restic forget --keep-daily 7 は host 単位で policy を適用します。同じ hostname を報告する 2 台のマシンは 1 台の host として扱われます。そのため、7 件の「daily」snapshot がすべてクローン由来になり、元のサーバーの snapshot が prune される可能性があります。最初の backup 実行前に hostname を修正するか、クローン上の timer を停止します。certbot の場合は、より単純です。同じ名前を更新する 2 台のサーバーが certificate authority の duplicate certificate rate limit に達し、後から実行した処理は、その名前の組み合わせに対してすでに発行された certificate が多すぎるというエラーで失敗します。クローンの domain がまだ元のサーバーを指している場合、HTTP challenge にも合格できません。そのため、クローン上では更新を無効にします。
monitoring agent を処理します。 多くの agent は hostname、またはインストール時に書き込まれた ID file で識別します。そのため、2 台の agent が同じ host として報告すると、metrics が 1 つの series に混在します。CPU graph には、どちらのマシンも実際には生成していない値が表示され、alert が頻繁に切り替わります。クローン上の agent を停止して削除するか、vendor が提供する手順に従い、新しい hostname で再登録します。
元のサーバーの address が network configuration に残っていないか確認します。 image の netplan に static address が設定されていると、クローンが別のマシンに割り当てられた IP を使用します。
ip -br addr
sudo grep -r addresses /etc/netplan/このクローンを template にする場合は、cloud-init の state を消去します。
sudo cloud-init clean --logsこれにより /var/lib/cloud 配下の cloud-init の state が削除されます。次回の起動時には first-boot module が再度実行され、SSH host key が存在しない場合は生成されます。バージョンによっては machine ID をリセットする flag も用意されています。別の場所にある flag の一覧をそのまま信頼せず、cloud-init clean --help を自分の image 上で実行して、対応している機能を確認してください。
どれを使うか
リスクの高いアップグレードをロールバックする場合: スナップショットを取得します。 変更の数分前にスナップショットを取得してからアップグレードを実行し、問題が発生した場合はイメージをリストアします。リストアすると、スナップショット取得後に行われたすべての書き込みが失われます。そのため、ライブトラフィックを処理しているサーバーでは、最初にデータベースをダンプし、どの時間帯のデータが失われる可能性があるかを正確に把握してください。10 分間オフラインにできるサーバー上の do-release-upgrade であれば、スナップショットだけで対応できます。
より大きなプランへ移行する場合: クローンをデプロイします。 スナップショットから、より大きなプラン上にクローンを構築します。前述の識別情報一覧を確認してから、トラフィックを切り替える前に専用の IP でテストします。切り替えを短時間で完了できるよう、前日に DNS TTL を短くしてください。新しいサーバーが実際のトラフィックを処理できるようになるまで、元のサーバーは稼働させておきます。両方のサーバーで同じベンチマーク方法を使用して、対象のワークロードで本当に性能が向上することを事前に確認してください。負荷の高いハードウェア上で vCPU が増えても、必ずしもアップグレードになるとは限りません。
テンプレートを作成する場合: クリーンアップ済みのマシンをスナップショットします。 1 台のサーバーをインストールして堅牢化し、イメージを作成する前に固有の情報をすべて削除します。ホストキーを残さず、machine ID を空にし、個人用の authorized_keys と認証情報を削除し、cloud-init もクリーンアップします。その状態でスナップショットを取得します。そのスナップショットからデプロイした各インスタンスは、初回起動時に独自の識別情報を生成します。そのため、前述のチェックリストを毎回確認する必要がなくなります。新しい VPS で最初の 10 分間に行う標準手順と組み合わせれば、本来繰り返す作業をテンプレートにあらかじめ含められます。
FAQ
VPS のスナップショットはバックアップですか?
いいえ。元のサーバーと同じ障害ドメインに属するためです。スナップショットはプロバイダーのストレージ上にあり、あなたのアカウント内で、通常は同じリージョンに保存されます。アカウントの停止、盗まれた API key、または誤ったインスタンス削除によって、サーバーとそのスナップショットが 1 回の操作で削除される可能性があります。多くのプロバイダーでは、仕様上、インスタンスを削除するとそのスナップショットも削除されます。スナップショットは利用できる最も高速なロールバック手段なので、継続して取得してください。また、プロバイダーが管理できないインフラストラクチャ上に、暗号化した 2 つ目のコピーも保持してください。
スナップショットを取得する前にデータベースを停止する必要がありますか?
必ずしも必要ではありません。ただし、取得される状態を受け入れる必要があります。プロバイダーのスナップショットはクラッシュ整合性を持ちます。これは、電源断後にディスクが示す状態と一致するイメージであるという意味です。PostgreSQL と InnoDB は起動時にそこから復旧し、PostgreSQL はその間に database system was not properly shut down; automatic recovery in progress を記録します。データが異なる時点でスナップショットを取得した 2 つのボリュームにまたがっている場合や、アプリケーションが fsync なしで書き込む場合、復旧は保証されません。まず pg_dumpall または mysqldump --single-transaction をディスクへ書き込み、イメージに整合性を確認できる 1 つのファイルが含まれるようにしてください。
2 台のクローンされたサーバーが同じ IP アドレスを奪い合うのはなぜですか?
/etc/machine-id を共有しているためです。DHCP を使用するイメージでは、systemd-networkd はデフォルトで machine ID から DHCP クライアント識別子を生成します。そのため、両方のクローンが同じクライアントとしてリースを要求し、DHCP サーバーは両方に同じアドレスを割り当てます。/etc/machine-id を 0 バイトに切り詰め、/var/lib/dbus/machine-id を削除してから /etc/machine-id に再度シンボリックリンクを作成し、再起動してください。systemd が新しい値を生成します。もう 1 つの一般的な原因は、/etc/netplan/ に静的アドレスが書き込まれていることです。クローンはその内容をそのままコピーしています。ip -br addr で確認してください。
クローンを本番環境に投入して安全かどうかを最速で確認する方法は何ですか?
4 つの項目を元のサーバーと比較してください。両方で ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub を実行し、フィンガープリントが異なることを確認します。両方で cat /etc/machine-id を実行し、値が異なることを確認します。hostnamectl status を実行し、名前が新しく、名前解決できることを確認してください。sudo が警告を出さないことも確認します。次に systemctl list-timers --all を実行し、バックアップ、証明書の更新、監視エージェントなど、共有システムと通信するすべてのタイマーを停止します。どのマシンがその処理を担当するか決めるまで停止しておいてください。