実践シェルスクリプト講座 第2回】データ消失の悲劇をゼロにする!「堅牢バックアップ」と「Slack通知」の鉄壁要塞

【実践シェルスクリプト講座 第2回】「消えちゃった…」では遅すぎる!自動バックアップと通知システムを構築しよう

こんにちは!「リナックス先生」です。
前回は、サーバー停止を防ぐための「ディスク容量監視」と「ログ掃除」を自動化しましたね。
コウ君、その後サーバーの調子はどう?

コウ君

先生!前回の監視スクリプトのおかげで、ディスク容量はバッチリ管理できています!
…でも、実はさっき、設定ファイルをいじっていて、うっかり rm コマンドで大事な設定ファイルを消してしまったんです…。
これ、Windowsの「ゴミ箱」みたいに戻せないんですか…?(涙)

リナックス先生

残念ながら、Linuxのコマンドラインで消したファイルは「即座に完全消滅」するわ。
人間だもの、ミスは必ず起きるし、HDDやSSDもいつかは必ず壊れる。
だからこそ、「壊れても昨日の状態に戻せる」仕組みを作っておくのが、エンジニアの最重要任務なのよ!

「実践シェルスクリプト講座」第2回のテーマは、インフラエンジニアの生命線である「バックアップ」です。
ただファイルをコピーするだけではなく、プロの現場で使われる「世代管理(古いバックアップの自動削除)」「rsyncによる高速同期」、そして失敗した瞬間にスマホに通知を飛ばす「Slack連携」まで、鉄壁のシステムを構築します。

本講座のカリキュラム(全5回)

第2回では、守りの要(かなめ)となる技術を習得します。

  1. サーバー停止の恐怖から解放!「ディスク容量監視」と「ログ自動整理」の完全自動化【完了】
  2. 【今回】データ消失をゼロにする!「堅牢バックアップ」と「Slack通知」の構築
  3. 手作業を撲滅せよ!「ユーザー一括作成」と「SSH鍵配布」の自動化スクリプト
  4. 攻撃者を秒でブロック!ログ解析による「SSH総当たり攻撃」自動防御システム
  5. プロの品質へ!スクリプトの「安全性向上(デバッグ)」と「トラブルシューティング」

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

【2026年最新】Linuxサーバー構築におすすめのVPS比較3選!現役エンジニアが速度とコスパで厳選
Linuxの勉強、まだ「自分のPC」でやって消耗していませんか?「Linuxを覚えたいけど、環境構築でエラーが出て先に進めない…」「VirtualBoxを入れたらパソコンが重くなった…」これは、Linux学習を始める9割の人がぶつかる壁です...

コメント