VPNウォッチドッグのログを書き手を壊さずにローテーションする
まず出力先を決め、次にライターがファイルを開く方法に合わせてローテーションを設計します。短命なチェックには名前変更、デーモンには再オープンかcopytruncateです。
VPNのヘルスチェックが毎分一行を追記すると、静かに二つ目の責務が生まれます。その行がディスクからどう出ていくかを決めることです。トンネルを直してもこの責務には何の効果もなく、ファイル名を変えても古いディスクリプタを保持したプロセスは向き直らないので、まず出力先の問いに答えてから、ライターの開き方に合わせてローテーションを設計します。
ローテーション方針より先に出力先を選ぶ
すでに出力をジャーナルへ送っているsystemdユニットなら、保持は一か所にまとまります。journaldはSystemMaxUseやMaxRetentionSecといった上限を適用するので、そこへ記録するウォッチドッグは追加作業なしで上限を継承します。その隣に無制限のファイルをもう一つ足すと、利得なしに保守が倍になります。
専用ファイルが場所を得るのは、ほかの何かが読むときです。パーサー、監査用のエクスポート、安定したパスを期待するページなどのために、ファイルには所有者、モード、ローテーション方針をまとめて書く必要があります。三つはそろって初めて正しくなるからです。誰でも作れるパスは誰でも置き換えられるパスでもあるので、ディレクトリの権限も同じメモに入れ、すべてをチェックの隣に書けば次の編集は同じ事実から始まります。
書き手に合わせてブロックを書く
呼び出しのたびにパスを開き、一行を追記して終了するライターから始めます。この形はプロセスの協力なしでローテーションを生き延び、次の実行はその時点でそのパスにあるファイルを開くので、下のブロックは一週間分の版を保持し、ライターが必要とする所有者とモードでファイルを作り直します。
/var/log/vpn-watchdog.log {
daily
rotate 7
create 0600 root root
missingok
notifempty
}
logrotateのマニュアルは、数を保持するローテーション済み版の数と定義しており、七日間の履歴を保証するものではありません。またdailyというキーワードは、独自のスケジューラとしてではなく、ジョブの実行時に評価されます。ほとんどのディストリビューションではsystemdタイマーかcronエントリがすでにその呼び出しを持っているので、二つ目を足す前にどちらがあるか確認します。保持数よりも所有権を合わせることのほうが重要です。root専用のモードをサービスアカウントが書くファイルにコピーすると、次のローテーションでログが止まり、障害はエラーではなく数時間後の古いタイムスタンプとして現れるので、よそからブロックをコピーする前にライターのユーザーを確認してください。
決して再オープンしない書き手には別の対処が要る
開いたディスクリプタを保持する長時間プロセスは、名前を変えたinodeへ書き続けます。容量は解放されず、見えるファイルは成長を止めます。これはinodeを確認するまで壊れたローテーションに見えるので、プロセスに再オープンを教えるか、名前変更ではなくコピーと切り詰めを行うようにします。
再オープンの道は、ソフトウェアが対応している場所ではよりきれいで、文書化されたシグナルを送るか文書化されたコマンドを実行し、プロセス自身に新しいファイルを開かせます。代わりがcopytruncateで、内容をローテーション後の名前にコピーしてから元のファイルをその場で切り詰めるため、ディスクリプタは有効なままですが、コピーと切り詰めの間に入った書き込みは失われます。
systemdが起動するユニットでは、StandardOutputをジャーナルかファイルに向け、ユニットにライフサイクルを持たせます。サイズ上限なしでtee -aを通して追記するシェルループは、失敗するチェックが健全なものよりはるかに多く記録することが多いため、障害時にパーティションを埋める配置です。
タイマーを待たずにローテーションを試す
デバッグモードは何が起きるかを報告し、何も触りません。納得したら実際のローテーションを強制し、次のログ行が名前変更後のファイルではなく新しいファイルに入ることを確認します:
logrotate -d /etc/logrotate.d/vpn-watchdog logrotate -f /etc/logrotate.d/vpn-watchdog ls -l /var/log/vpn-watchdog.log* tail -n 3 /var/log/vpn-watchdog.log df -h /var/log
出力はその順に読み、デバッグ実行はファイルと意図する動作を名指しするはずです。強制実行は期待したローテーション名を生むはずです。tailはライターが再び動いた後に新しいエントリを示すはずで、dfの値が増え、見えるファイルが縮むなら、上で述べたディスクリプタの問題です。
版がすでにあるときに保持がどう振る舞うかを見るため、強制実行を二回繰り返します。最も古いファイルが削除されるのが期待される結果で、削除がない場合は通常、保持数かローテーション名のワイルドカードがlogrotateの作ったものと合っていません。
ロールバックと記録を残す
編集前に前のブロックを保存し、どのスケジューラが実行するかを記録します。ファイルだけを戻してタイマーを戻さないロールバックは、元の問題の半分を再現するからです。復元後に一度手でローテーションしてライターのアカウントがログのパスに書けることを確認し、サービスがsystemd配下で動くなら出力のリダイレクトを変えた後にユニットを再読み込みします。動いているプロセスは古い宛先へ書き続けるので、ユニットがきれいに戻ったと信じるのではなく、次の数行のログを自分で読んでください。
モード、所有者、保持数、想定するディスク予算をチェックのすぐ隣に記録します。後でチェックが単発スクリプトからデーモンに変わるとき、ローテーション方針も一緒に変える必要があり、そのメモが次の人にどの前提が崩れたかを伝えます。
最後に、ログの判断を復旧の判断から切り離しておきます。失敗したチェックは接続自身の復旧方針に従うべきで、logrotateの中に隠れた規則に従うべきではなく、どのログ配置も、動いているトンネルを再起動する理由になってはいけません。