【実践シェルスクリプト講座 第2回】「消えちゃった…」では遅すぎる!自動バックアップと通知システムを構築しよう
こんにちは!「リナックス先生」です。
前回は、サーバー停止を防ぐための「ディスク容量監視」と「ログ掃除」を自動化しましたね。
コウ君、その後サーバーの調子はどう?
先生!前回の監視スクリプトのおかげで、ディスク容量はバッチリ管理できています!
…でも、実はさっき、設定ファイルをいじっていて、うっかり rm コマンドで大事な設定ファイルを消してしまったんです…。
これ、Windowsの「ゴミ箱」みたいに戻せないんですか…?(涙)
残念ながら、Linuxのコマンドラインで消したファイルは「即座に完全消滅」するわ。
人間だもの、ミスは必ず起きるし、HDDやSSDもいつかは必ず壊れる。
だからこそ、「壊れても昨日の状態に戻せる」仕組みを作っておくのが、エンジニアの最重要任務なのよ!
「実践シェルスクリプト講座」第2回のテーマは、インフラエンジニアの生命線である「バックアップ」です。
ただファイルをコピーするだけではなく、プロの現場で使われる「世代管理(古いバックアップの自動削除)」や「rsyncによる高速同期」、そして失敗した瞬間にスマホに通知を飛ばす「Slack連携」まで、鉄壁のシステムを構築します。
本講座のカリキュラム(全5回)
第2回では、守りの要(かなめ)となる技術を習得します。
- サーバー停止の恐怖から解放!「ディスク容量監視」と「ログ自動整理」の完全自動化【完了】
- 【今回】データ消失をゼロにする!「堅牢バックアップ」と「Slack通知」の構築
- 手作業を撲滅せよ!「ユーザー一括作成」と「SSH鍵配布」の自動化スクリプト
- 攻撃者を秒でブロック!ログ解析による「SSH総当たり攻撃」自動防御システム
- プロの品質へ!スクリプトの「安全性向上(デバッグ)」と「トラブルシューティング」
1. バックアップにおける「2つの戦略」
一口に「バックアップ」と言っても、データの種類によって最適な方法は異なります。
今回は、以下の2つのパターンに対応したスクリプトを作成します。
| 戦略 | 使うコマンド | 特徴 | 向いているデータ |
|---|---|---|---|
| まるごとアーカイブ | tar |
複数のファイルを1つの圧縮ファイルにまとめる。「その時点の状態」を保存するのに最適。 | 設定ファイル(/etc)、Webサイトのソースコード、DBダンプ |
| 同期(ミラーリング) | rsync |
変更があったファイルだけを転送する。高速で、常に「最新の状態」を別の場所に保つのに最適。 | 画像・動画データ、ユーザー投稿ファイル、巨大なディレクトリ |
2. 「tar」で作る世代管理付きバックアップ
まずは基本の tar コマンドを使ったバックアップです。
しかし、ただ毎日圧縮しているだけでは、すぐにディスクがいっぱいになってしまいます。
そこで、「過去7日分だけ残して、それより古いものは自動で捨てる」という「世代管理機能」を実装します。
スクリプト作成:backup_tar.sh
WordPressなどのWebデータ(/var/www/html)をバックアップする想定で作成します。vim backup_tar.sh を作成しましょう。
#!/bin/bash
# --- 安全設定 ---
# set -e : エラー発生時に即停止
# set -u : 未定義変数の使用禁止
# set -o pipefail : パイプ(|)の途中でエラーがあっても検知する
set -euo pipefail
# ==========================================
# 設定エリア
# ==========================================
# バックアップ元ディレクトリ
SOURCE_DIR="/var/www/html"
# バックアップ保存先
BACKUP_DIR="/backup/www_data"
# ファイル名の接頭辞
PREFIX="webapp_backup"
# 保存期間(日)
RETENTION_DAYS=7
# 今日の日付(ファイル名に使用)
DATE_STR=$(date +%Y%m%d_%H%M)
# 出力ファイル名
OUTPUT_FILE="${BACKUP_DIR}/${PREFIX}_${DATE_STR}.tar.gz"
# ==========================================
# メイン処理
# ==========================================
echo "=== バックアップ開始: $DATE_STR ==="
# 1. 保存先ディレクトリがなければ作成
if [ ! -d "$BACKUP_DIR" ]; then
echo "ディレクトリ作成: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
fi
# 2. tarコマンドで圧縮アーカイブ作成
# c: 作成, z: gzip圧縮, v: 詳細表示, f: ファイル出力
# P: 絶対パス警告を抑制(復元時に注意が必要だが便利)
echo "圧縮中: $SOURCE_DIR -> $OUTPUT_FILE"
tar -czf "$OUTPUT_FILE" -P "$SOURCE_DIR"
echo "バックアップ作成完了: $OUTPUT_FILE"
# 3. 古いバックアップの削除(世代管理)
echo "保存期間(${RETENTION_DAYS}日)を過ぎたファイルを検索して削除します..."
# findコマンドの -mtime +N は「N日より前」という意味
find "$BACKUP_DIR" -name "${PREFIX}_*.tar.gz" -mtime +$RETENTION_DAYS -type f -delete -print
echo "=== 全処理完了 ==="
💡 プロの解説:set -o pipefail とは?
通常、Bashはパイプライン command1 | command2 を実行した際、command1 が失敗しても command2 が成功すれば「成功」とみなしてしまいます。set -o pipefail を宣言しておくと、パイプの途中で一つでもコケたら「失敗」と判定してくれるため、予期せぬバックアップミス(空ファイルの生成など)を防ぐことができます。
3. プロ御用達「rsync」による高速同期バックアップ
画像ファイルが何万枚もある場合、毎回 tar で固めていては時間がかかりすぎます。
そこで登場するのが、変更差分だけを賢く転送する最強ツール rsync です。
ここでは、「外付けHDD(または別パーティション)」にデータをミラーリングする シナリオでスクリプトを書きます。
スクリプト作成:backup_rsync.sh
vim backup_rsync.sh を作成します。
#!/bin/bash
# 安全設定
set -eu
# ==========================================
# 設定エリア
# ==========================================
SOURCE_DIR="/home/user/images/"
# 外付けHDDなどのマウントポイント
DEST_DIR="/mnt/backup_disk/images_mirror/"
# ロックファイル(多重起動防止用)
LOCK_FILE="/var/run/backup_rsync.lock"
# ログファイル
LOG_FILE="/var/log/backup_rsync.log"
# ==========================================
# 多重起動防止チェック
# ==========================================
# rsyncは時間がかかるため、前の処理が終わる前に次のcronが走ると
# サーバーが激重になります。それを防ぐ「排他制御」です。
if [ -f "$LOCK_FILE" ]; then
# ロックファイルがある=実行中
# ただし、前の処理が異常終了してファイルだけ残っている場合もあるのでPID確認
PID=$(cat "$LOCK_FILE")
if ps -p "$PID" > /dev/null; then
echo "【SKIP】前回の処理(PID:$PID)が実行中のためスキップします。" >> "$LOG_FILE"
exit 0
else
echo "【WARN】ロックファイルが残っていましたがプロセスはいません。続行します。" >> "$LOG_FILE"
fi
fi
# ロックファイル作成(自分のPIDを書き込む)
echo $$ > "$LOCK_FILE"
# スクリプト終了時(成功・失敗問わず)に必ずロックファイルを消す設定
trap 'rm -f "$LOCK_FILE"' EXIT
# ==========================================
# rsync実行
# ==========================================
echo "=== 同期開始: $(date) ===" >> "$LOG_FILE"
# -a: アーカイブモード(権限や日付を維持する・必須!)
# -v: 詳細表示
# --delete: 元にないファイルは削除する(完全同期)
# --exclude: 除外したいファイル
rsync -av --delete --exclude '.cache' "$SOURCE_DIR" "$DEST_DIR" >> "$LOG_FILE" 2>&1
echo "=== 同期完了: $(date) ===" >> "$LOG_FILE"
--delete オプションって便利そうですけど、ちょっと怖くないですか?
もし元のファイルを間違って全部消した状態で実行したら、バックアップ先も全部消えちゃいますよね?
その通り!だから rsync は「人為的ミス」のカバーには向かないの。
あくまで「ハードウェア故障への備え」と割り切るか、重要なデータは tar と併用するのが正解よ。
初めて実行する時は必ず --dry-run(テストモード)を付けて、何が消えるか確認するのが鉄則ね。
4. 失敗したら教えて!「通知機能」の実装
Cronでバックアップを自動化しても、失敗したことに気づかなければ意味がありません。
「バックアップ取れてると思ったら、半年前に止まってました」というのが、インフラエンジニアが最も顔面蒼白になる瞬間です。
そこで、スクリプトが失敗した時だけ、チャットツール(今回はSlackやDiscord、Teamsなどで使えるWebhook)に通知を送る汎用関数を作成しましょう。
Webhook URLの取得
Slackの場合、「Incoming Webhook」アプリを追加して、https://hooks.slack.com/services/xxxxx... というURLを取得しておいてください。
(Discordなども同様の手順で取得できます)
通知機能付きスクリプトの完成形
先ほどの backup_tar.sh に、エラーハンドリングと通知機能を組み込みます。
#!/bin/bash
set -u # set -e は外して、自分でエラーハンドリングする
# ==========================================
# 設定エリア
# ==========================================
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
SERVER_NAME=$(hostname)
# ... (その他の変数は前述と同じ) ...
# ==========================================
# 通知用関数
# ==========================================
send_alert() {
local message=$1
# JSON形式でメッセージを作成
# Slack向け(textフィールド)
local payload="{\"text\": \"🚨 [${SERVER_NAME}] バックアップ失敗!\n${message}\"}"
# curlでPOST送信
curl -s -X POST -H 'Content-type: application/json' --data "$payload" "$WEBHOOK_URL"
}
# ==========================================
# メイン処理
# ==========================================
# ログファイルにエラー出力もまとめる
exec > >(tee -a "/var/log/backup.log") 2>&1
echo "--- バックアップ開始 ---"
# tarコマンド実行(条件分岐で成否判定)
if tar -czf "$OUTPUT_FILE" -P "$SOURCE_DIR"; then
echo "成功しました。"
else
# 失敗時のみここに来る
echo "【ERROR】tarコマンドが失敗しました!"
send_alert "tarコマンドの実行に失敗しました。ログを確認してください。"
exit 1
fi
# 古い削除
if ! find "$BACKUP_DIR" -name "${PREFIX}_*.tar.gz" -mtime +$RETENTION_DAYS -delete; then
echo "【WARN】古いファイルの削除に失敗しました。"
# 削除失敗は致命的ではないので通知はしない(運用による)
fi
echo "--- 完了 ---"
この仕組みを入れておけば、「便りがないのは良い便り(成功)」という運用が可能になり、毎日のログ確認の手間から解放されます。
5. 復旧(リストア)できなきゃ意味がない
最後に、一番重要なことを伝えます。
「リストア(復元)テストをしていないバックアップは、バックアップではない」。
いざ本番でデータが消えた時、焦ってコマンドを打ち間違え、バックアップファイルすら壊してしまう…なんてことはよくあります。
以下の手順で、正常に戻せるかを必ず確認してください。
tarファイルの解凍テスト手順
# 1. テスト用の一時ディレクトリを作る(絶対に本番ディレクトリでやらない!) mkdir /tmp/restore_test cd /tmp/restore_test # 2. バックアップファイルをコピーしてくる cp /backup/www_data/webapp_backup_20240125.tar.gz . # 3. 解凍コマンド実行 # x: eXtract(解凍) # -C: 解凍先ディレクトリを指定(超重要!これがないと元の場所に上書きされる可能性あり) tar -xzvf webapp_backup_20240125.tar.gz -C . # 4. 中身がちゃんとあるか確認 ls -R
⚠️ 恐怖の「絶対パス」トラップ
作成時に -P オプション(絶対パス保存)を使っている場合、解凍時にも -P をつけると、いきなり /var/www/html にファイルを上書きしてしまう危険があります。
テスト時は -P を付けずに解凍するか、tar -tf ファイル名 で中身のパス構造を確認してから作業するのがプロの鉄則です。
まとめ:守りの要を自動化せよ
今回は、エンジニアにとって最も重要な「バックアップ」を自動化しました。
| ツール | 用途 | 重要オプション |
|---|---|---|
tar |
時点ごとの保存(世代管理) | czvf (作成), find -mtime (削除) |
rsync |
ディレクトリ同期(ミラーリング) | -av --delete, lockfile (排他制御) |
curl |
外部への通知送信 | -X POST, JSON形式 |
これで、第1回の「監視」、第2回の「保護」が完了しました。
あなたのサーバーは、かなり「堅牢」になってきましたね。
次回予告:面倒な「ユーザー登録」を1秒で終わらせる
サーバーが安定してきたら、次は「人」の管理です。
「新入社員10人分のユーザー作成とSSH鍵の設定、やっといて」と言われたら…手作業でやりますか?
次回は、CSVファイルの名簿を読み込んで、ユーザーアカウントとパスワード、SSH設定を一括で自動生成するスクリプトを作成します。
コウ君、今日作ったスクリプトもCronに仕込むのを忘れないでね。
おすすめは、アクセスの少ない深夜(AM 3:00〜5:00くらい)よ。
バックアップさえあれば、失敗なんて怖くない。どんどん新しいことに挑戦していきましょ!
▼スクリプトの実験場に最適!推奨VPS


コメント