セキュリティ

「落ちても自力で戻る」構成にする — systemd の自動復旧と監視の最小セット

引き継いだシステムで最も多い障害が、プロセスが落ちたまま復旧しないというものです。
原因は難しい話ではなく、たいていは「自動で戻る仕組みが入っていない」だけです。

まず systemd に任せる

[Service]
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=300
StartLimitBurst=5
  • Restart=on-failure … 異常終了時に再起動
  • RestartSec=5 … 5秒待ってから(即時だと連続失敗で負荷が跳ねる)
  • StartLimitBurst … 300秒に5回を超えたら諦める

最後の2行が地味に重要です。これが無いと、
設定ミスで起動できない状態のとき、無限に再起動を繰り返して CPU を食い潰します

再起動で自動復帰させるのも忘れずに。

systemctl enable --now myapp
systemctl is-enabled myapp   # → enabled

メモリで落ちる場合

OOM Killer に殺されている場合、Restart=on-failure では戻らないことがあります
(シグナル終了は「失敗」扱いにならない設定があるため)。確実に戻したいなら:

Restart=always

同時に、Gunicorn 側でワーカーを定期的に入れ替えるとメモリリークが緩和されます。

max_requests = 1000
max_requests_jitter = 100

jitter を入れないと全ワーカーが同時に再起動して、その瞬間だけ応答が詰まります。

落ちたことに気づく仕組み

自動復旧が入っていても、復旧できない障害は必ず起きます。
最小の死活監視として、外形監視を1つ入れておきます。

#!/bin/sh
# 5分ごとに cron で実行
CODE=$(curl -s -o /dev/null -w '%{http_code}' -m 10 https://example.com/health/)
if [ "$CODE" != "200" ]; then
    echo "health check failed: $CODE" | mail -s "[ALERT] site down" you@example.com
fi

サーバー自身から監視すると、サーバーごと落ちたときに気づけません。
可能なら別のホストから叩くようにします。

Django 側のヘルスチェックは、DB まで見るようにしておきます。

def health(request):
    try:
        connections["default"].cursor().execute("SELECT 1")
    except Exception:
        return HttpResponse("db error", status=503)
    return HttpResponse("ok")

「Nginx は生きているが DB が死んでいる」状態を検知できるようになります。

ログの肥大でディスクが埋まる事故も防ぐ

意外と多いのがこれです。/var/log が埋まって書き込めなくなり、サービスが止まります。

/var/log/myapp/*.log {
    daily
    rotate 14
    compress
    missingok
    notifempty
    copytruncate
}

まとめ

  • Restart=StartLimitBurst をセットで入れる(無限再起動を防ぐ)
  • systemctl enable を忘れない(サーバー再起動で戻らない事故の大半はこれ)
  • 別ホストからの外形監視を1本。ヘルスチェックは DB まで見る
  • logrotate を入れてディスク枯渇を防ぐ

どれも数行の設定ですが、これが入っているかどうかで運用の安心感がまったく変わります。