深夜2時、沈黙したデータベースバックアップとCrontabの苦い教訓
午前2時15分ちょうど、監視システムからのアラートでスマートフォンが激しく震えました。/backupパーティションの使用率が98%に達したという警告です。4TBのNASストレージへデータを同期する処理が、1行のエラーログも吐かないまま丸4時間完全に応答停止(ハングアップ)していました。
急いで本番環境のCentOS Stream 9クラスタにSSHログインし、crontab -lを実行してみると、バックアップスケジュールはそのまま残り、5つのアスタリスクで構成された構文にも一切誤りはありませんでした。続いて/var/log/cronを開くと、記録されていたのは「22:00にジョブが起動した」という、たった1行の通知のみ。手掛かりも、終了コード(exit code)も、Pythonスクリプトがどの処理ステップで息絶えたのかも全く分かりませんでした。
cronの致命的な弱点は、「撃ちっぱなし(fire-and-forget)」の仕組みにあります。cronはバックグラウンドでプロセスを起動すると、そのまま放置してしまいます。スクリプトのメモリリークによって8GBのRAMを使い果たし、LinuxのOOM Killerによってプロセスが無慈悲に強制終了(kill)されても、cronは完全に沈黙したままです。原因を突き止めるために、各所に散らばった何十本ものログファイルを手作業で探り当てるしかありません。この痛烈な経験を経て、私たちのチームは重大な決断を下しました。新規インフラ環境においてcrontabを完全撤廃し、その代わりにSystemd ServiceとSystemd Timerの強力な組み合わせへ移行することにしたのです。
コアコンセプト:なぜSystemd TimerはCrontabより優れているのか?
crontabのような単一のコマンドライン記述とは異なり、systemdではタスクの自動化を2つの独立した要素に分割して管理します。
- Service Unit (.service): 「何を実行するか」を定義します。スクリプトのファイルパス、実行ユーザー権限、環境変数、CPU/RAMの上限クォータなどを指定します。
- Timer Unit (.timer): 「いつ起動するか」を定義します。固定スケジュールでの実行、一定間隔での繰り返し実行、あるいはOS起動完了の5分後にトリガーするといった柔軟な時間指定が可能です。
このモジュール型アーキテクチャにより、実際の運用現場において圧倒的なアドバンテージが得られます。
- journalctlによるログの一元集約:
stdout(標準出力)とstderr(標準エラー出力)の両ストリームが自動的にsystemd journalへ集約されます。>> /var/log/app.log 2>&1のような面倒なリダイレクト設定をしなくても、ミリ秒単位でエラーの足跡をトレース可能です。 - 厳格なリソース制御: Cgroupsを利用してリソース上限を簡単に制限できます。例えば「スクリプトが消費できるメモリを最大512MB、CPUコアを最大50%に制限する」といった指定が可能になり、メインアプリケーションの安定稼働を確実に保護できます。
- 実行漏れタスクのキャッチアップ(実行補完): サーバーのメンテナス再起動がバックアップ実行時刻と重なってしまっても、
Persistent=trueオプションを設定しておけば、OSの起動完了直後にスキップされたタスクを即座に補完実行してくれます。 - ランダムな負荷分散:
RandomizedDelaySecパラメータにより、数百台規模のサーバーで実行タイミングをランダムにずらし、一斉実行によるディスクI/Oのボトルネックを防止できます。
ハンズオン実践:CentOS Stream 9でSystemd Service & Timerを構築する
ここでは実用的なシナリオとして、「7日以上前の古い一時ログを自動的に圧縮・クリーンアップするスクリプトを作成し、サービス化して毎日深夜02:00に定期実行する」手順を解説します。
ステップ1:実行スクリプトの準備
システムの運用スクリプトを配置するための標準ディレクトリを作成します:
sudo mkdir -p /opt/ops-scripts
sudo nano /opt/ops-scripts/cleanup-backup.sh
スクリプトの内容を記述します:
#!/bin/bash
set -euo pipefail
NOW=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$NOW] INFO: ログのチェックとクリーンアップを開始します..."
# tempディレクトリ内の7日以上経過した古いログファイルを検索して削除
DELETED_COUNT=$(find /tmp -type f -name "*.log" -mtime +7 -print -delete | wc -l)
echo "[$NOW] SUCCESS: 合計 $DELETED_COUNT 件のログファイルをクリーンアップしました。"
実行権限を付与し、アクセス権をrootユーザーのみに制限します:
sudo chmod 750 /opt/ops-scripts/cleanup-backup.sh
sudo chown root:root /opt/ops-scripts/cleanup-backup.sh
ステップ2:Systemd Service Unitの作成
/etc/systemd/system/配下に新しいunitファイルを作成します:
sudo nano /etc/systemd/system/cleanup-backup.service
サービス設定を定義します:
[Unit]
Description=システムのバックアップおよび古いログのクリーンアップ
After=network.target local-fs.target
[Service]
Type=oneshot
User=root
ExecStart=/opt/ops-scripts/cleanup-backup.sh
MemoryMax=512M
CPUQuota=50%
技術的な注意点: 処理が一度実行されて終了するバッチジョブには、必ずType=oneshotを指定してください。独立したType=oneshotに対してRestart=on-failureディレクティブを使用すると、systemdが起動を拒否します。実行スケジュールの管理はTimer側に完全に任せるのが原則です。
ステップ3:Systemd Timer Unitの作成
systemdが自動的に連携できるよう、サービスファイルと同名のタイマーファイルを作成します:
sudo nano /etc/systemd/system/cleanup-backup.timer
タイマー設定を定義します:
[Unit]
Description=午前2時にcleanup-backup.serviceを起動するタイマー
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=10m
Unit=cleanup-backup.service
[Install]
WantedBy=timers.target
主要パラメータの解説:
OnCalendar=*-*-* 02:00:00:毎日02:00にタスクをトリガーします。Persistent=true:前回の実行時刻をディスクに記録します。午前02:00時点でサーバーがオフラインだった場合でも、次回サーバー起動直後にジョブが自動補完実行されます。RandomizedDelaySec=10m:0〜10分のランダムな遅延を発生させ、ディスクI/O負荷を均等に分散します。
ステップ4:設定のリロードとTimerの有効化
systemdに新しいunitファイルを再スキャンさせます:
sudo systemctl daemon-reload
タイマーを有効化し、即座に起動します(サービスではなくタイマーのみを有効化します):
sudo systemctl enable --now cleanup-backup.timer
CentOS Stream 9上でタイマーのキュー状態を確認します:
systemctl list-timers --all | grep cleanup-backup
次回実行日時や残り時間のカウントダウンが分かりやすく出力されます:
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-11-06 02:07:14 UTC 13h left n/a n/a cleanup-backup.timer cleanup-backup.service
ステップ5:テストと迅速なデバッグ
テストのためにシステムクロックを変更したり、深夜2時まで起きて待つ必要はありません。サービスを直接手動実行してロジックを検証できます:
sudo systemctl start cleanup-backup.service
実行ステータスコードを確認します:
systemctl status cleanup-backup.service
journalctlを使って完全な実行ログを確認します:
sudo journalctl -u cleanup-backup.service -e --no-pager
tail -fのようにリアルタイムでログを追跡(tailing)します:
sudo journalctl -u cleanup-backup.service -f
おわりに
systemd timerの記述は、1行で済むcrontabに比べて2つのファイルを作成する必要があるため、導入の手間は確かに増えます。しかし、それによって得られる対価は計り知れません。journalctlによるログの一元管理、RAM/CPUの厳格な上限設定、そして信頼性の高い実行補完メカニズムが手に入ります。CentOS Stream 9サーバーが数十台規模にスケールしたとき、システムの堅牢性を支え、エンジニアが夜安心してぐっすり眠れるようにしてくれるのは、まさにこの圧倒的な安定性なのです。
