堅牢なBashスクリプト設計:setオプションとtrapによる障害波及の最小化

Tech

{ “style”: “Technical SRE/DevOps Documentation”, “focus”: “Robust Shell Scripting & Error Handling”, “tools”: [“bash”, “jq”, “curl”, “systemd”], “principles”: [“Safe-fail”, “Resource Cleanup”, “Idempotency”] } 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

堅牢なBashスクリプト設計:setオプションとtrapによる障害波及の最小化

【導入と前提】

API連携やデータ加工を伴うバッチ処理において、障害発生時の連鎖停止とリソース解放を確実に実行する手法を解説します。

  • OS: GNU/Linux (Ubuntu 22.04 LTS / RHEL 9 等)

  • ツール: Bash 4.4+, curl, jq, systemd

【処理フローと設計】

graph TD
A["開始"] --> B["シェルオプション・Trap設定"]
B --> C["一時ファイルの作成"]
C --> D["外部APIからデータ取得: curl"]
D --> E["JSONデータの加工: jq"]
E --> F["メイン処理の実行"]
F --> G["正常終了"]
D -- エラー発生 --> H["Trap発動"]
E -- エラー発生 --> H
F -- エラー発生 --> H
H --> I["一時ファイルの削除・通知"]
I --> J["異常終了コードで停止"]

スクリプト開始直後にエラーハンドリングを定義し、どのフェーズで失敗しても一時ファイルやロックファイルが残留しないように設計します。また、パイプライン途中のエラーを検知可能にします。

【実装:堅牢な自動化スクリプト】

#!/usr/bin/env bash

# -e: エラー発生時に即座に終了


# -u: 未定義変数の参照時にエラー


# -o pipefail: パイプ内のコマンド失敗を全体の戻り値に反映

set -euo pipefail

# ロギング関数

log() {
    echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')] $1"
}

# 一時ファイルの定義

TMP_JSON=$(mktemp /tmp/api_data.XXXXXX.json)

# クリーンアップ関数: EXITシグナルを受けて必ず実行

cleanup() {
    local exit_code=$?
    log "Cleaning up temporary files..."
    rm -f "$TMP_JSON"
    log "Process finished with exit code: $exit_code"
}

# スクリプト終了時にcleanup関数を呼び出し

trap cleanup EXIT

# メイン処理

log "Starting data synchronization..."

# 外部APIからのデータ取得例


# -s: 進捗非表示, -S: エラー時は表示, -f: HTTPエラーで失敗扱い


# --retry: 通信失敗時のリトライ回数

API_URL="https://api.example.com/v1/metrics"
curl -sSfL --retry 3 --max-time 10 "$API_URL" -o "$TMP_JSON"

# jqを用いたデータ検証と加工


# -e: 抽出結果が空またはfalseの場合に終了ステータス1を返す

if ! jq -e '.status == "success"' "$TMP_JSON" > /dev/null; then
    log "Error: Invalid API response status."
    exit 1
fi

# 特定の値を抽出して処理

TARGET_VALUE=$(jq -r '.data.value' "$TMP_JSON")
log "Extracted value: ${TARGET_VALUE}"

# ここに本番の更新処理などを記述

log "Operation completed successfully."

systemdによる自動実行管理

定期実行が必要な場合は、systemd.timer を活用して実行制御とログ集約を行います。

# /etc/systemd/system/data-sync.service

[Unit]
Description=Robust Data Sync Script
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/data-sync.sh
User=appuser
Group=appuser

# /etc/systemd/system/data-sync.timer

[Unit]
Description=Run data-sync every hour

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

【検証と運用】

正常系の確認

スクリプトを手動実行し、終了コードが 0 であること、および一時ファイルが削除されていることを確認します。

bash data-sync.sh
echo $?  # 0が表示されること
ls /tmp/api_data.*  # ファイルが存在しないこと

異常系の確認(ログ確認)

systemd経由で実行した場合、標準出力・標準エラー出力は journalctl に集約されます。

# 特定のサービス・ユニットのログを確認

journalctl -u data-sync.service -n 50 --no-pager

【トラブルシューティングと落とし穴】

  1. trap のスコープ: trap は定義されたシェルプロセスに対して有効です。サブシェル内のエラーでメインシェルの trap を動かしたい場合は、戻り値を適切にハンドリングする必要があります。

  2. set -e の限界: if 文の条件式や while などの制御構造内で実行されるコマンドが失敗しても、スクリプトは停止しません。明示的なエラーチェックが必要です。

  3. 環境変数の管理: APIキーなどの機密情報をスクリプト内にハードコードせず、systemdEnvironmentFile やシークレット管理ツールから読み込むようにしてください。

  4. 一時ファイルの肥大化: mktemp で作成したファイルが非常に大きい場合、/tmp(tmpfs)のメモリ圧迫に注意が必要です。

【まとめ】

運用の冪等性と堅牢性を維持するための3つのポイント:

  1. 早期停止 (Fail-Fast): set -euo pipefail を定型文として含め、予期せぬ挙動を即座に遮断する。

  2. 確実な後始末: trap ... EXIT を利用し、成功・失敗に関わらずリソース(ファイル、ロック、一時DB)を解放する。

  3. 外部依存の考慮: curl のリトライや jq のバリデーションを用い、外部要因による不安定さをスクリプト層で吸収する。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました