内容
小さな bash スクリプトが deploy.sh、backup.sh、restore-test.sh の三本、共通のアラートヘルパーが一本、それらをつなぐ設定ファイル一式です。短いのでひと通り読み切れますし、スクリプトが呼び出すツール以外に入れるものはありません。
| ファイル | 役割 |
|---|---|
deploy.sh | リリースを clone してビルドし、ヘルスチェックを通し、失敗したらロールバックします。モードは restart(シンボリックリンクの切り替えと pm2 の再起動)と bluegreen(下のテストで失敗リクエストなし)です。blue/green モードでは deploy.sh rollback で元に戻せます。 |
backup.sh | SQLite と Postgres を安全にダンプしてから、restic でバックアップします(既定で日次 7、週次 4、月次 6 を保持)。リポジトリのチェックもします。ダンプが壊れていれば、スナップショットを作る前に停止します。 |
restore-test.sh | 最新のスナップショットを一時ディレクトリにリストアし、データベースのダンプを検証して、任意で指定した自前のチェックを実行し、データのサンプルを読み出します。 |
notify.sh | どれかのスクリプトが失敗したときに、Telegram のメッセージか Webhook の POST を送ります。成功時は何も送りません。 |
| 設定 | kit.env.example、Caddyfile.example、ecosystem.config.js、cron.example |
ダウンタイムなしのデプロイを計測
bluegreen モードでは、新しいリリースを空いているポートで起動し、直接ヘルスチェックを通します。そのあと Caddy をグレースフルリロードで新しい側に切り替え、最後に古いリリースを止めます。ヘルスチェックを通らないリリースには、トラフィックが一切届きません。
テスト条件は次のとおりです。Debian 12 のコンテナ、apt の Caddy 2.6.2、pm2、起動に 1.5 秒かかる Node アプリ。デプロイの間じゅう Caddy 経由で毎秒20リクエストをかけ続けます(五つに一つは 300 ms かかるエンドポイント)。接続エラー、3 秒を超えるタイムアウト、200 以外のステータスのいずれかを失敗として数えます。
| シナリオ | リクエスト数 | 失敗 | デプロイ時間 |
|---|---|---|---|
restart モード、正常なリリース | 116 | 32(すべて HTTP 502) | 2.4 s |
restart モード、壊れたリリース(ロールバック) | 380 | 67(すべて HTTP 502) | 16.8 s |
bluegreen、正常なリリース | 211 | 0 | 7.6 s |
bluegreen、壊れたリリース(ヘルスチェックが 500 を返す) | 373 | 0 | 16.6 s |
bluegreen、起動時にクラッシュするリリース | 371 | 0 | 16.5 s |
bluegreen、rollback | 208 | 0 | 7.5 s |
正常な blue/green の実行では、負荷が両方のバージョンに当たっています(v1 が 115 件、続けて v2 が 96 件を返しました)。トラフィックが実際に移ったことが確認できます。数回実行したところ、restart モードは 31〜35 件が失敗し、blue/green は一件も失敗しませんでした。
実際にリストアして確かめたバックアップ
- データベースは正しい方法でダンプします。 SQLite はオンラインバックアップと整合性チェック、Postgres は
pg_dump -Fcとpg_restore -lを使います。稼働中のデータベースファイルをそのままコピーすると、壊れたバックアップになりがちです。 - 壊れたダンプでは止まります。 テストでは、壊した SQLite ファイルを渡すと、スナップショットを作らずにバックアップが中断されました。
- 古いダンプは紛れ込みません。 設定から外したデータベースが、新しいスナップショットに入り続けることはありません。
restore-test.shが確かめます。 リストアし、ダンプを検証し、サンプルに対してrestic checkを実行します。cron から週に一度回してください。- リモートリポジトリ。 どちらのスクリプトも
sftp:のリポジトリで動きます(ローカルの OpenSSH サーバーで確認済みです)。
失敗アラート
TELEGRAM_BOT_TOKEN と TELEGRAM_CHAT_ID、または NOTIFY_WEBHOOK_URL を設定します。デプロイ、バックアップ、リストアテストのどれかが失敗すると、チャンネルごとにメッセージを一通だけ送ります。成功したときは何も送りません。トークンは標準入力で curl に渡すので、画面には出力されません。通知 API に届かなくても、スクリプトの終了コードは変わりません。
セットアップ手順
restic、git、curl、pm2を入れます(blue/green ならcaddy、SQLite や Postgres を使うならsqlite3かpostgresql-clientも)。- フォルダをサーバーにコピーし、
cp kit.env.example kit.envして編集します。 - リポジトリのパスワードを、パーミッション 600 のファイルに置きます。
./backup.sh、./restore-test.sh、./deploy.shの順に実行します。cron.exampleの cron の行を登録します。
どうテストしたか
クリーンな Debian 12 のコンテナ(Node 22、restic、PostgreSQL 15、apt の Caddy 2.6.2 と OpenSSH、pm2)で、59 のチェックを実行しています。対象は、フォルダ、SQLite、Postgres のバックアップ、リストアと行の突き合わせ、間違ったリポジトリ、壊れたデータベース、ダンプの欠落、負荷をかけながらの両モードでのデプロイとロールバック、ローカルの偽 API に対するアラート、SFTP 経由の restic です。
想定している使い手
Linux の VPS で、Node か Go のアプリを一つか二つ動かしている個人開発者や小さなチームです。Kubernetes を導入せずに、信頼できるデプロイとバックアップを手に入れたい方に向いています。
動作要件
Linux、bash、pm2、restic が必要で、blue/green には Caddy も使います。アプリは PORT からポートを読む必要があります。blue/green は数秒間、二つのリリースを並べて動かすので、データベースのマイグレーションには後方互換性が必要です。
よくある質問
ダウンタイムなしのデプロイに、このキットは必須ですか?
いいえ。手法はこの記事で、主要なコマンドとあわせて説明しています。キットは、それにヘルスチェック、ロールバック、状態の管理、アラート、テストスイートを加えてまとめたものです。
どんな構成を前提にしていますか?
Linux、bash、pm2、blue/green 用の Caddy、バックアップ用の restic、そして環境変数 PORT からポートを読むアプリです。
Postgres と SQLite の両方に対応していますか?
はい。どちらもダンプして検証します。
複数のアプリで使えますか?
アプリごとに kit.env を用意し、KIT_CONF で使うファイルを指定します。
ライセンスはどうなっていますか?
ご自身のサーバーで使えます。ファイルの再販はできません。
変更履歴
- 0.3.0: ロールバック付きの blue/green デプロイ、失敗アラート(Telegram と Webhook)、SFTP リポジトリのテスト、すべてのメッセージを英語に統一。
- 0.2.0:
backup.shに SQLite と Postgres のダンプを追加、restore-test.shにダンプの検証を追加。 - 0.1.0: 初回リリース。
続きを読む
- Zero-downtime deploys on one VPS with Caddy and pm2 (英語)
- restic forget and prune: how the keep flags decide (英語)
- A restic backup recipe for SQLite and Postgres on one VPS (英語)
- Monitoring backups so that silence never means success (英語)
- The 3-2-1 backup rule applied to a single VPS (英語)
入手する
支払い、税、請求書は販売代行事業者の Polar が処理します。決済後にダウンロードリンクが届きます。