<p><meta/>
{
“role”: “SRE/DevOps Engineer”,
“focus”: “Robust CLI Automation & Idempotency”,
“technologies”: [“bash”, “jq”, “curl”, “systemd”, “atomic-file-ops”],
“security”: “Credential management & Temp file cleanup”,
“version”: “1.1”
}
</p>
<p>本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">外部API連携における冪等性を担保する状態管理CLIスクリプトの実装ガイド</h1>
<h3 class="wp-block-heading">【導入と前提】</h3>
<p>APIリクエストの二重実行を防止し、中断後の再開を可能にする「状態ファイル方式」の自動化スクリプトを構築します。</p>
<ul class="wp-block-list">
<li><p><strong>OS:</strong> GNU/Linux (POSIX準拠のシェル環境)</p></li>
<li><p><strong>ツール:</strong> <code>curl</code> (API通信), <code>jq</code> (JSON処理), <code>flock</code> (排他制御)</p></li>
</ul>
<h3 class="wp-block-heading">【処理フローと設計】</h3>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["スクリプト開始"] --> B{"ロックファイル確認"}
B -- ロック取得失敗 --> C["終了: 二重起動防止"]
B -- ロック取得成功 --> D["状態ファイル読み込み"]
D --> E{"対象タスクは完了済?"}
E -- YES --> F["スキップして正常終了"]
E -- NO --> G["APIリクエスト実行"]
G --> H{"レスポンス確認"}
H -- 成功 --> I["一時状態ファイル作成"]
I --> J["アトミックな移動 mv"]
J --> K["終了"]
H -- 失敗 --> L["エラー処理/リトライ"]
</pre></div>
<p>この設計の核心は、<strong>「API成功」と「ローカルの状態更新」の乖離を最小限にするアトミックなファイル操作</strong>にあります。<code>mv</code> コマンドは同一ファイルシステム内においてシステムコールレベルで原子性が保証されるため、不完全な状態ファイルが作成されるリスクを排除できます。</p>
<h3 class="wp-block-heading">【実装:堅牢な自動化スクリプト】</h3>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/usr/bin/env bash
# --- 設定・定数 ---
set -euo pipefail # エラーで停止、未定義変数禁止、パイプ途中のエラーを拾う
readonly LOCK_FILE="/tmp/api_sync.lock"
readonly STATE_FILE="./state.json"
readonly API_ENDPOINT="https://api.example.com/v1/resource"
readonly API_KEY_FILE="./.api_key" # 認証情報はファイルから読み込む
# --- トラップ設定 (クリーンアップ) ---
# スクリプト終了時に一時ファイルを削除し、ロックを解放する
TEMP_STATE=$(mktemp ./state.json.XXXXXX)
trap 'rm -f "$TEMP_STATE" "$LOCK_FILE"' EXIT
# --- 二重起動の防止 ---
# flockを用いて、同一スクリプトが複数走らないように制御
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
echo "Error: Another instance is running." >&2
exit 1
fi
# --- 冪等性の確認 ---
# jqを使用して現在のステータスを確認
if [[ -f "$STATE_FILE" ]]; then
LAST_STATUS=$(jq -r '.status // "none"' "$STATE_FILE")
if [[ "$LAST_STATUS" == "completed" ]]; then
echo "Task already completed. Skipping."
exit 0
fi
fi
# --- APIリクエスト実行 ---
echo "Sending request to API..."
RESPONSE=$(curl -s -f -L \
--retry 3 \
--retry-delay 2 \
-H "Authorization: Bearer $(cat "$API_KEY_FILE")" \
-X POST "$API_ENDPOINT" \
-d '{"action": "sync"}')
# -s: 進捗非表示, -f: HTTPエラー時に異常終了, -L: リダイレクト追従, --retry: 一時エラー時リトライ
# --- 状態のアトミックな更新 ---
# 新しい状態を一度一時ファイルに書き込み、mvで置換(アトミック更新)
echo "$RESPONSE" | jq '. + {status: "completed", timestamp: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))}' > "$TEMP_STATE"
mv "$TEMP_STATE" "$STATE_FILE"
echo "Sync completed successfully."
</pre>
</div>
<h4 class="wp-block-heading">定期実行のための systemd Timer 設定例</h4>
<p><code>/etc/systemd/system/api-sync.service</code></p>
<div class="codehilite">
<pre data-enlighter-language="generic">[Unit]
Description=API Sync Task with Idempotency
[Service]
Type=oneshot
User=sre-user
WorkingDirectory=/opt/scripts
ExecStart=/usr/bin/bash /opt/scripts/api_sync.sh
</pre>
</div>
<p><code>/etc/systemd/system/api-sync.timer</code></p>
<div class="codehilite">
<pre data-enlighter-language="generic">[Unit]
Description=Run API Sync every 5 minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
</pre>
</div>
<h3 class="wp-block-heading">【検証と運用】</h3>
<ol class="wp-block-list">
<li><p><strong>正常系の確認</strong></p>
<ul>
<li><p>スクリプトを初動させ、<code>state.json</code> が生成されることを確認。</p></li>
<li><p>再度実行し、”Task already completed.” と表示されAPIが呼ばれないことを確認。</p></li>
</ul></li>
<li><p><strong>異常系の確認</strong></p>
<ul>
<li>APIエンドポイントを無効なURLに変え、リトライ処理とスクリプトの停止(<code>set -e</code>)を確認。</li>
</ul></li>
<li><p><strong>ログの確認</strong></p>
<div class="codehilite">
<pre data-enlighter-language="generic"># systemd経由で実行している場合
journalctl -u api-sync.service -f
</pre>
</div></li>
</ol>
<h3 class="wp-block-heading">【トラブルシューティングと落とし穴】</h3>
<ul class="wp-block-list">
<li><p><strong>ファイルシステムの境界:</strong> <code>mv</code> コマンドをアトミックに動作させるため、一時ファイル(<code>mktemp</code>)は必ず <code>STATE_FILE</code> と<strong>同じパーティション(ディレクトリ)</strong>内に作成してください。</p></li>
<li><p><strong>権限エラー:</strong> 実行ユーザーがディレクトリに対して書き込み権限を持っているか確認してください。特に <code>systemd</code> で実行する場合は <code>User=</code> の設定が重要です。</p></li>
<li><p><strong>環境変数の露出:</strong> <code>curl</code> の引数に直接トークンを書かず、上記例のようにファイル読み込みや環境変数経由(<code>export</code>)にし、<code>ps</code> コマンドで見えないように配慮しています。</p></li>
</ul>
<h3 class="wp-block-heading">【まとめ】</h3>
<p>運用の冪等性を維持するための3つのポイント:</p>
<ol class="wp-block-list">
<li><p><strong>実行前チェック:</strong> APIを叩く前に、ローカルの状態ファイルで「やるべき仕事が残っているか」を判定する。</p></li>
<li><p><strong>アトミックな更新:</strong> 中途半端な書き込みを避けるため、一時ファイルに書き出してから <code>mv</code> で置換する。</p></li>
<li><p><strong>確実なクリーンアップ:</strong> <code>trap</code> を使い、異常終了時でも一時ファイルやロックファイルが残らないようにする。</p></li>
</ol>
{
“role”: “SRE/DevOps Engineer”,
“focus”: “Robust CLI Automation & Idempotency”,
“technologies”: [“bash”, “jq”, “curl”, “systemd”, “atomic-file-ops”],
“security”: “Credential management & Temp file cleanup”,
“version”: “1.1”
}
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
外部API連携における冪等性を担保する状態管理CLIスクリプトの実装ガイド
【導入と前提】
APIリクエストの二重実行を防止し、中断後の再開を可能にする「状態ファイル方式」の自動化スクリプトを構築します。
OS: GNU/Linux (POSIX準拠のシェル環境)
ツール: curl (API通信), jq (JSON処理), flock (排他制御)
【処理フローと設計】
graph TD
A["スクリプト開始"] --> B{"ロックファイル確認"}
B -- ロック取得失敗 --> C["終了: 二重起動防止"]
B -- ロック取得成功 --> D["状態ファイル読み込み"]
D --> E{"対象タスクは完了済?"}
E -- YES --> F["スキップして正常終了"]
E -- NO --> G["APIリクエスト実行"]
G --> H{"レスポンス確認"}
H -- 成功 --> I["一時状態ファイル作成"]
I --> J["アトミックな移動 mv"]
J --> K["終了"]
H -- 失敗 --> L["エラー処理/リトライ"]
この設計の核心は、「API成功」と「ローカルの状態更新」の乖離を最小限にするアトミックなファイル操作にあります。mv コマンドは同一ファイルシステム内においてシステムコールレベルで原子性が保証されるため、不完全な状態ファイルが作成されるリスクを排除できます。
【実装:堅牢な自動化スクリプト】
#!/usr/bin/env bash
# --- 設定・定数 ---
set -euo pipefail # エラーで停止、未定義変数禁止、パイプ途中のエラーを拾う
readonly LOCK_FILE="/tmp/api_sync.lock"
readonly STATE_FILE="./state.json"
readonly API_ENDPOINT="https://api.example.com/v1/resource"
readonly API_KEY_FILE="./.api_key" # 認証情報はファイルから読み込む
# --- トラップ設定 (クリーンアップ) ---
# スクリプト終了時に一時ファイルを削除し、ロックを解放する
TEMP_STATE=$(mktemp ./state.json.XXXXXX)
trap 'rm -f "$TEMP_STATE" "$LOCK_FILE"' EXIT
# --- 二重起動の防止 ---
# flockを用いて、同一スクリプトが複数走らないように制御
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
echo "Error: Another instance is running." >&2
exit 1
fi
# --- 冪等性の確認 ---
# jqを使用して現在のステータスを確認
if [[ -f "$STATE_FILE" ]]; then
LAST_STATUS=$(jq -r '.status // "none"' "$STATE_FILE")
if [[ "$LAST_STATUS" == "completed" ]]; then
echo "Task already completed. Skipping."
exit 0
fi
fi
# --- APIリクエスト実行 ---
echo "Sending request to API..."
RESPONSE=$(curl -s -f -L \
--retry 3 \
--retry-delay 2 \
-H "Authorization: Bearer $(cat "$API_KEY_FILE")" \
-X POST "$API_ENDPOINT" \
-d '{"action": "sync"}')
# -s: 進捗非表示, -f: HTTPエラー時に異常終了, -L: リダイレクト追従, --retry: 一時エラー時リトライ
# --- 状態のアトミックな更新 ---
# 新しい状態を一度一時ファイルに書き込み、mvで置換(アトミック更新)
echo "$RESPONSE" | jq '. + {status: "completed", timestamp: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))}' > "$TEMP_STATE"
mv "$TEMP_STATE" "$STATE_FILE"
echo "Sync completed successfully."
定期実行のための systemd Timer 設定例
/etc/systemd/system/api-sync.service
[Unit]
Description=API Sync Task with Idempotency
[Service]
Type=oneshot
User=sre-user
WorkingDirectory=/opt/scripts
ExecStart=/usr/bin/bash /opt/scripts/api_sync.sh
/etc/systemd/system/api-sync.timer
[Unit]
Description=Run API Sync every 5 minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
【検証と運用】
正常系の確認
異常系の確認
- APIエンドポイントを無効なURLに変え、リトライ処理とスクリプトの停止(
set -e)を確認。
ログの確認
# systemd経由で実行している場合
journalctl -u api-sync.service -f
【トラブルシューティングと落とし穴】
ファイルシステムの境界: mv コマンドをアトミックに動作させるため、一時ファイル(mktemp)は必ず STATE_FILE と同じパーティション(ディレクトリ)内に作成してください。
権限エラー: 実行ユーザーがディレクトリに対して書き込み権限を持っているか確認してください。特に systemd で実行する場合は User= の設定が重要です。
環境変数の露出: curl の引数に直接トークンを書かず、上記例のようにファイル読み込みや環境変数経由(export)にし、ps コマンドで見えないように配慮しています。
【まとめ】
運用の冪等性を維持するための3つのポイント:
実行前チェック: APIを叩く前に、ローカルの状態ファイルで「やるべき仕事が残っているか」を判定する。
アトミックな更新: 中途半端な書き込みを避けるため、一時ファイルに書き出してから mv で置換する。
確実なクリーンアップ: trap を使い、異常終了時でも一時ファイルやロックファイルが残らないようにする。
コメント