堅牢な自動化:`set -euo pipefail` と `trap` による障害耐性の高いシェルスクリプト構築

Tech

[META][TYPE:TECHNICAL_GUIDE][LANG:JA][TARGET:SRE_DEVOPS][TOPIC:SHELL_SCRIPT_ROBUSTNESS][STANCE:BEST_PRACTICE]

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

堅牢な自動化:set -euo pipefailtrap による障害耐性の高いシェルスクリプト構築

【導入と前提】

API経由で取得したJSONを解析し、設定ファイルを安全にアトミック更新するバッチ処理を堅牢化します。

  • 実行環境: GNU/Linux (Ubuntu 22.04 LTS / RHEL 9 推奨)

  • 必須ツール: Bash 4.4+, curl 7.6x+, jq 1.6+

【処理フローと設計】

graph TD
    A["開始"] --> B["一時ファイルの生成 mktemp"]
    B --> C["APIデータ取得 curl"]
    C --> D{"取得成功?"}
    D -- No --> E["エラー終了/trap発動"]
    D -- Yes --> F["JSONパース/フィルタリング jq"]
    F --> G["設定ファイルのアトミック置換 mv"]
    G --> H["正常終了/一時ファイル削除"]
    E --> I["後処理: 残存テンポラリ削除"]

スクリプト実行中にエラーが発生した場合、set -e で即時停止し、trap が仕掛けておいた一時ファイルのクリーンアップ処理を確実に実行させる設計です。

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

#!/usr/bin/env bash

# --- 厳格モードの設定 ---

set -euo pipefail

# -e: コマンドの失敗時に即座に終了


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


# -o pipefail: パイプライン途中のエラーを伝播

# --- グローバル設定 ---

readonly API_URL="https://api.example.com/v1/config"
readonly CONF_DEST="/etc/myapp/config.json"
readonly LOG_TAG="config_updater"

# --- 一時ファイル管理(trap活用) ---

TMP_FILE=$(mktemp /tmp/config_update.XXXXXX.json)

# 終了時(正常/異常問わず)に一時ファイルを削除

trap 'rm -f "$TMP_FILE"; echo "[$(date)] Cleanup completed."' EXIT

log() {

    # systemd-journald等と連携しやすいログ出力

    echo "[$(date)] [$LOG_TAG] $1"
}

main() {
    log "INFO: Starting configuration update..."

    # 1. データ取得 (curl)


    # -s: 進捗非表示, -L: リダイレクト追従, -f: HTTPエラー時に失敗として扱う


    # --retry: 通信エラー時の再試行回数

    if ! curl -sLf --retry 3 "$API_URL" -o "$TMP_FILE"; then
        log "ERROR: Failed to fetch data from API."
        exit 1
    fi

    # 2. バリデーションとパース (jq)


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

    if ! jq -e '.status == "active"' "$TMP_FILE" > /dev/null; then
        log "ERROR: Invalid configuration received or status not active."
        exit 2
    fi

    # 3. アトミックなファイル置換


    # 同一パーティション内の mv はアトミック(原子性)が保証される

    log "INFO: Updating configuration file..."
    chmod 644 "$TMP_FILE"
    mv "$TMP_FILE" "$CONF_DEST"

    log "SUCCESS: Configuration updated to $CONF_DEST."
}

main "$@"

systemdによる定期実行例 (/etc/systemd/system/config-update.timer)

[Unit]
Description=Daily Config Sync

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

【検証と運用】

  1. 構文チェック: bash -n update_config.sh で静的解析を実行します。

  2. ドライラン(擬似エラー発生): API_URL を存在しないドメインに変更し、set -etrap によって一時ファイルが残らずに終了することを確認します。

  3. ログ確認: systemd経由で実行した場合、以下のコマンドで実行履歴を確認可能です。

    journalctl -u config-update.service -t config_updater
    

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

  • パイプラインの罠: curl ... | jq ... と書くと、set -e だけでは curl の失敗を検知できません。必ず set -o pipefail を併用してください。

  • sudoの扱い: スクリプト内で sudo を多用すると、PATH や環境変数が引き継がれず、jq が見つからないエラーが発生することがあります。サービス実行時は User= 指定で権限を管理することを推奨します。

  • 一時ファイルの権限: mktemp はデフォルトで 600 等の厳しい権限で作成されるため、最後に chmod で適切な読み取り権限を付与しないと、アプリケーションが設定を読み込めなくなる場合があります。

【まとめ】

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

  1. アトミック更新: 直接ファイルを上書きせず、mktemp で作成したファイルを mv で置換することで、不完全な状態の設定ファイルが参照されるのを防ぐ。

  2. 即時停止と清掃: set -euo pipefail で「失敗を隠さない」設定にし、trap で「後片付けを自動化」する。

  3. 外部依存の検証: jq 等を用いて、書き込み前に内容の妥当性(バリデーション)を必ず行う。

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

コメント

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