想定している読者
Go のバイナリをサーバーに置き、systemd で動かしている方に向けたキットです。いまのデプロイは scp、systemctl restart、ログをざっと確認、という流れでしょう。それでも動きますが、忙しい時間帯に再起動が重なって Caddy が数秒間 502 を返したり、起動はするのにすべてのリクエストが失敗するビルドを出してしまい、それがトラフィックを受けるのを誰も止めてくれなかったりします。
このキットは、新しいリリースを古いものと並べて起動し、トラフィックを受ける前に検査し、切り替え、新しいリリースがおかしくなれば自動で戻すデプロイスクリプトに置き換えます。すでに販売している Node と pm2 向けキットの、Go と systemd 版です。pm2 を使っていない、使いたくない方に向いています。
内容
deploy.sh: アトミックなcurrentシンボリックリンクを使うリリースフォルダ、CI で作ったバイナリかサーバー上でのビルドのデプロイ、ヘルスチェック(N 回連続で応答)、切り替え、ドレイン、自動ロールバック、deploy.sh rollback、そして二つのデプロイが重ならないようにするロック。- ハードニング済みの systemd ユニット:
Type=notify、グレースフルな停止、再起動ポリシー、権限のないユーザー、サービスユーザーが読めないパーミッション 600 の環境ファイルに置くシークレット。 - ユーザー、ディレクトリ、ユニット、環境ファイルを用意するインストーラー。
- グレースフルシャットダウンと、ソケットアクティベーションと
sd_notifyのテスト済みヘルパーを備えた、標準ライブラリだけの小さなサンプル Go アプリ。 - デプロイ失敗時の Telegram か Webhook へのアラート。成功したデプロイは通知しません。
- テストハーネスと、直近の実行の出力。
切り替え方式は二つ
blue/green(既定)。 app@a と app@b の二つのインスタンスを Caddy の背後に置きます。新しいリリースは空いているポートで起動し、Caddy に知らせる前に、ヘルスチェックに何度も続けて応答しなければなりません。そのあと Caddy をグレースフルにリロードし、古いインスタンスをドレインして停止します。切り替えのあとに新しいリリースがおかしくなったら、スクリプトがすべてを元に戻します。ロールバックは、同じ手順を逆にたどるだけです。KEEP_PREVIOUS=1 を付けると古いインスタンスを温めたまま残すので、ロールバックは Caddy の切り替えだけで済みます。
ソケットアクティベーション。 サービスを再起動している間も、systemd が待ち受けソケットを開いたままにするので、接続は拒否されずキューに入ります。新しいリリースはまず別のポートでカナリアとして動かすため、壊れたビルドが再起動まで届くことはありません。
blue/green を既定にしているのは、最も遅いリクエストの所要時間を、そのリクエスト自身の長さに近く保てるからです。ソケットモードは、インスタンスを二つ動かせない場合のためにあります。動かせない固定ポートや、一つのプロセスしか持てないリソースがある場合です。下の計測は両方のモードを対象にしています。
計測結果
条件は次のとおりです。systemd 252 を PID 1 とする Debian 12 のコンテナ、Caddy 2.6.2、起動に 1.5 s かかるサンプルアプリ、Caddy 経由で一定の毎秒20リクエスト、シナリオごとに三回の実行。接続エラー、タイムアウト、200 以外のステータスのいずれかを、失敗として数えます。
| シナリオ | 送信数 | 失敗 |
|---|---|---|
ふつうの systemctl restart、インスタンスは一つ | 152〜162 | 33〜42(すべて HTTP 502) |
同じく、Caddy の lb_try_duration 5s を使用 | 162 | 0、ただし最も遅いリクエストは 2.3 s かかった |
| blue/green、正常なデプロイ | 293〜295 | 0 |
blue/green、/health が 500 を返すリリース | 458〜460 | 0 |
| blue/green、起動時にクラッシュするリリース | 132 | 0 |
| blue/green、切り替え後におかしくなるリリース | 381〜383 | 0(ロールバックまでに、問題のあるリリースが 182 か 183 件に応答) |
| blue/green、ロールバック | 293〜295 | 0 |
blue/green、KEEP_PREVIOUS=1 でのロールバック | 222〜223 | 0、トラフィックは 0.1 s 後に移動 |
| blue/green、2 s のリクエストが処理中の正常なデプロイ | 234 | 0、遅いリクエストは切られずに完了 |
| ソケットモード、正常なデプロイ | 268〜269 | 0 |
| ソケットモード、2 s のリクエストが処理中の正常なデプロイ | 307〜310 | 0、最も遅いリクエストは 5.65 s |
blue/green モードでのデプロイは 8〜9 秒で、トラフィックが新しいリリースに移るのは約 3.6 秒後です。これらは、このアプリとこのマシンでの数値です。
デプロイの流れ
README では七つの手順を順に説明しています。要点は次のとおりです。ロックを取り、リリースフォルダを作り、空いているインスタンスを起動して ready の報告を待ち、直接ヘルスチェックを行い、新しい upstream を書いて Caddy をリロードし、シンボリックリンクを切り替え、待ってもう一度確認し、そのあとで初めて古いインスタンスを止めます。各ステップに失敗時の経路が決めてあり、重要なものはテスト済みです。ヘルスチェックを通らないリリース、起動時のクラッシュ、切り替え後におかしくなるリリースです。
動作要件
systemd の動く Linux、Caddy、curl、flock。サーバー上でビルドする場合のみ Go と git も必要です。一つのホストで一つのサービスを扱います。
よくある質問
Node と pm2 を使っています。これは自分向けですか?
いいえ。その構成には VPS Deploy & Backup Kit が同じ役割を果たします。
Caddy は必要ですか?
blue/green では切り替えに必要です。計測は、どちらのモードも Caddy を前段に置いて行いました。
Ubuntu でも動きますか?
Ubuntu も systemd を使っており、必要なのは systemd と Caddy だけなので適しています。動作確認は Debian 12 で行いました。
デプロイ実行中に、もう一度デプロイしたらどうなりますか?
アラートを送らず、コード 75 ですぐに終了します。ロックの働きです。
ライセンス
ご自身のサーバーで使えます。ファイルの再販はしないでください。
変更履歴
- 0.1.0: 初回リリース。
続きを読む
- What a plain systemctl restart costs a Go service under load (英語)
- systemd socket activation: no dropped requests, but a cost (英語)
- Roll back a bad deploy in a minute with release folders (英語)
- Database migrations that survive a zero-downtime deploy (英語)
- Deploy health checks that catch bad releases (英語)
- Caddy blue/green config for a one-server app, line by line (英語)
入手する
支払い、税、請求書は販売代行事業者の Polar が処理します。決済後にダウンロードリンクが届きます。