markdown

はじめに (対象読者・この記事でわかること)

この記事は、Linuxサーバの運用・保守を担うエンジニアや、自前のVPSでサービスを動かしている個人開発者を対象にしています。
/var/log/nginx/access.log やアプリケーションのトレースログなど、追記モードで永遠に肥大化し続けるファイルに対して、「再起動なし・サービス停止なし」で容量をゼロに戻す方法をお伝えします。
加えて、> logfiletruncate -s 0 がなぜ危険なのか、どうすればファイルディスクリプタを保持したまま安全にカラにできるのか、裏側の仕組みも解説します。

前提知識

  • Linuxの基本的なコマンド(ls, tail, ps)が使える
  • ファイルディスクリプタとプロセスの関係をざっくり理解している
  • sudo> リダイレクトの挙動を知っている

単純に > logfile してもダメな理由

多くのブログやStack Overflowの回答は「> /path/to/logfile すればOK」と書かれていますが、本番環境ではこれが原因でログが一切書き込めなくなることがあります。
これは、書き込み側のプロセスがファイル名ではなく既存のファイルディスクリプタを使っているため、ファイル自体が削除・切り替わっても、プロセスは古いinodeを参照し続けるからです。
結果、ディスク容量は空いても、実際にはログが「どこか別の場所」に書き続けられ、あとで lsof | grep deleted で巨大なデリテッドファイルを発見、という悲劇が起きます。

安全にtruncateする3つの方法と実装例

ここでは、サービス稼働中にログをクリアする3パターンを示します。
すべてCentOS 7/8、Ubuntu 20/22、Amazon Linux 2で動作確認済みです。

ステップ1: 最速ワンライナー(ログローテーション不要)

対象プロセスが SIGUSR1SIGRTMIN+1 によってファイルを再オープンしてくれる場合(nginx、apache、gunicorn など多くのWeb系ソフト)は、以下の2行で完結します。

Bash
# 1. ファイルサイズを0に truncate -s 0 /var/log/nginx/access.log # 2. プロセスに再オープンを促す kill -USR1 $(cat /run/nginx.pid)

truncate -s 0 はファイルの中身をカラにしながらinodeを変えないため、プロセスがfdを保持していても問題ありません。
その後 USR1 シグナルにより、nginxは「今開いているログファイルを一旦flushしてからクローズし、次のタイミングで新規作成」してくれます。
これでログは途切れず、ディスク容量も回復します。

ステップ2: シグナル対応外のプログラム向け(アトミックrename)

アプリケーション側がログリオープンに対応していない場合、以下の手順で「旧ファイルをrename→空ファイル作成→アプリケーション再起動なし」が可能です。

Bash
LOG=/var/log/app/app.log OLD="${LOG}.$(date +%Y%m%d-%H%M%S)" # 1. 現在のファイルをリネーム(ファイルディスクリプタは生きたまま) mv "$LOG" "$OLD" # 2. アプリが次の書き込みで新規ファイルを作るよう、同名の空ファイルを作成 install -m 644 /dev/null "$LOG" # 3. 古いファイルを圧縮・削除(任意) gzip "$OLD" && mv "$OLD.gz" /archive/

この方法のポイントは、mv によって「ディレクトリエントリ名だけが変わり、inodeは生きている」ことです。
アプリケーションは今まで通り古いfdに書き続けますが、次回のログローテーションタイミング(通常は再起動 or ログリオープン)までに旧ファイルを削除すればOKです。
これにより、ログの書き込み損失をゼロにしながら、即座にディスク容量を確保できます。

ハマった点とエラー解決

  1. Permission denied が出る
    ログファイルが root:root 600 などの場合、一般ユーザーで truncate しても失敗します。
    sudo truncate -s 0 /path するか、logrotate 用に専用グループを作って chgrp adm /path; chmod 0640 /path しておくと運用が楽です。

  2. tail -f しててもログが止まる
    tail -f も古いfdを見ているため、rename 後に新規ログが表示されなくなります。
    解決策は tail -F(大文字)を使うことです。tail -F はファイルがrename/再作成されても新しい方を自動的に追いかけてくれます。

  3. ディスク容量は空いたのに df -i で inode が不足している
    ログを gzip してそのままにしておくと、inode は開放されません。
    lsof | grep deleted で「deleted」状態のファイルが大量にあれば、プロセス再起動が必要です。
    定期的に logrotatecopytruncate オプションを使うと、これを防げます。

より本格的な運用には logrotate を使う

上記ワンライナーで十分な場面もありますが、複数ファイルや日付ごとの保存、圧縮、メール通知などを自動化したい場合は /etc/logrotate.d/ に設定を書くのが定石です。

Text
/var/log/nginx/*.log { daily rotate 14 compress delaycompress missingok notifempty sharedscripts postrotate # nginx 再起動なしでログリオープン [ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid) endscript }

copytruncate オプションを使わずに postrotatekill -USR1 しているので、inode の切り替えが確実かつ、古いファイルが残りません。

まとめ

本記事では、Linuxで追記型の巨大ログファイルを「サービス停止なし・ログ欠損なし」で truncate する方法を解説しました。

  • > filerm; touch はファイルディスクリプタの関係で思わぬトラブルを引き起こす
  • truncate -s 0 + SIGUSR1 が最速・最短のワンライナー
  • アプリがシグナルに対応していない場合は mv によるアトミックrenameで対応
  • 本格運用では logrotate を使い、postrotate で適切にログリオープンする

これで、ディスク容量不足で深夜のオンコールを食らうことも、ログローテーションのためにサービスを一瞬止める必要もなくなります。
次回は「systemd-journald の巨大な /var/log/journal を安全にクリアする方法」について掘り下げていく予定です。

参考資料