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

Tech

{ “focus”: “Robust Shell Scripting & SRE Best Practices”, “author”: “SRE_Agent_Gemini”, “context”: “System Automation/DevOps”, “tools”: [“bash”, “jq”, “curl”, “systemd”, “mermaid”] }

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

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

【導入と前提】

APIから取得したJSONデータを処理し、不測の事態でも一時ファイルを残さず安全に終了する、冪等性を担保した自動化スクリプトの設計指針を解説します。

  • 実行環境: GNU/Linux (Bash 4.4以降推奨)

  • 必須ツール: curl (データ取得), jq (JSONパース), mktemp (安全な一時ファイル作成)

【処理フローと設計】

graph TD
A["スクリプト開始"] --> B["set/trapの設定"]
B --> C["mktempによる一時領域確保"]
C --> D["curlで外部データ取得"]
D --> E{"jqでの検証・加工"}
E -->|Success| F["本番環境への反映/処理"]
E -->|Failure| G["trap発動: クリーンアップ"]
F --> H["正常終了"]
G --> I["エラー終了/通知"]

この設計の核心は、「どのフェーズでエラーが起きても、システムの状態を不整合なまま放置しない」ことにあります。特に、パイプラインの途中でエラーが発生した場合の検知漏れを防ぐことが重要です。

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

#!/usr/bin/env bash

# --- 安全のための初期設定 ---

set -e          # コマンドが失敗(終了ステータス0以外)したら即座に終了
set -u          # 未定義の変数を参照しようとしたらエラー
set -o pipefail # パイプラインの途中のコマンド失敗もエラーとして扱う

# --- クリーンアップ処理の定義 ---

TMP_FILE=$(mktemp /tmp/api_data.XXXXXX)

cleanup() {
    local exit_status=$?
    echo "Cleaning up temporary files..."
    rm -f "$TMP_FILE"
    echo "Script finished with status: $exit_status"
}

# スクリプト終了時に必ずcleanup関数を実行(正常・異常問わず)

trap cleanup EXIT

# --- メイン処理 ---

API_URL="https://api.example.com/v1/config"

echo "Fetching configuration from API..."

# curlのオプション解説:


# -s: 進捗を表示しない(Silent)


# -S: エラー時はメッセージを表示


# -L: リダイレクトに従う


# --retry 3: 通信失敗時に3回までリトライ


# --fail: HTTPレスポンスコード400/500番台でエラー終了させる

if ! curl -sSL --retry 3 --fail "$API_URL" -o "$TMP_FILE"; then
    echo "Error: Failed to fetch data from $API_URL" >&2
    exit 1
fi

# jqのオプション解説:


# -e: 抽出結果が空(null/false)の場合に終了ステータス1を返す


# -r: クォートなしの生出力

echo "Processing JSON data..."
TARGET_VALUE=$(jq -e -r '.settings.threshold' "$TMP_FILE")

# 実際の処理(ここでは例として値を出力)

echo "The threshold is: $TARGET_VALUE"

# 冪等性を考慮したファイル更新(例:一時ファイルから最終的な設定へ)


# atomic move(mv)を使うことで、書き込み途中の不完全なファイルを防ぐ


# mv "$TMP_FILE" /etc/myapp/config.json

systemd ユニットファイル設定例

このスクリプトを定期実行する場合、systemd のタイマー機能を利用してリソース制限とログ管理を統合します。

[Unit]
Description=Robust Config Sync Service

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

# セキュリティ設定

PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target

【検証と運用】

正常系の確認

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

./sync_config.sh
echo $?  # 0が表示されること
ls /tmp/api_data.* # 何も表示されないこと

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

systemd 経由で実行している場合、journalctl を用いて失敗時の詳細なトレースを確認します。

# 失敗した最新のログを表示

journalctl -u sync_config.service -n 50 --no-pager

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

  1. パイプラインと grep の罠: set -o pipefail 環境下では、grep でマッチするものがない場合に終了ステータス 1 となり、スクリプトが止まります。マッチしないことが許容される場合は grep ... || true と記述して回避します。

  2. 一時ファイルのパーミッション: mktemp はデフォルトで 600 (所有者のみ読み書き) で作成されます。別ユーザーが読み取る必要がある場合は、作成直後に chmod が必要です。

  3. sudo 実行時の環境変数: sudo を経由すると PATH や独自の環境変数が引き継がれないことがあります。必要に応じて sudo -E を使うか、スクリプト内で明示的に PATH を定義してください。

【まとめ】

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

  1. Atomic Operations: ファイル更新は直接編集せず、mv コマンドで一気に入れ替える。

  2. Explicit Cleanup: trap を使い、どんな終了条件でも残骸(ゴミ)を残さない。

  3. Strict Mode: set -euo pipefail を「おまじない」ではなく、障害検知の必須装備として全スクリプトに組み込む。

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

コメント

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