本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
構造化データ(JSON/YAML)の相互変換による設定ファイル自動生成の堅牢化
【導入と前提】
JSONソースからYAML形式の設定ファイルを安全に自動生成し、環境差分を吸収するSRE向けパイプラインの構築を自動化します。
OS: GNU/Linux (Ubuntu/RHEL/Debian)
必須ツール:
jq: JSON処理(v1.6以上推奨)yq: YAML処理(mikefarah/yq v4以上)curl: リモートソース取得
【処理フローと設計】
graph TD
A["Remote JSON Source"] -->|curl --retry| B("Local Temporary File")
B -->|jq validation| C{"Syntax Check"}
C -->|Invalid| D["Error Log & Alert"]
C -->|Valid| E["yq Transformation"]
E -->|Merge Env Vars| F["Final YAML Config"]
F -->|Atomic Write| G["Service Restart/Reload"]
ソース取得: リトライ処理を含む
curlで外部APIまたはリポジトリからJSONを取得。検証:
jqを用いて、変換前にスキーマやシンタックスが正しいかを確認。変換・マージ:
yqでJSONをYAMLへ変換し、環境固有の変数(.env等)と結合。反映: アトミックなファイル更新を行い、整合性を担保。
【実装:堅牢な自動化スクリプト】
#!/usr/bin/env bash
# --- 設定とエラーハンドリング ---
set -euo pipefail
# -e: エラーが発生した時点でスクリプトを終了
# -u: 未定義の変数を使用しようとしたらエラー
# -o pipefail: パイプラインの途中のエラーを無視せず終了
# 一時ファイル管理
TMP_JSON=$(mktemp /tmp/config_XXXXXX.json)
TMP_YAML=$(mktemp /tmp/config_XXXXXX.yaml)
DEST_CONFIG="/etc/myapp/config.yaml"
# 終了時に必ず一時ファイルを削除
trap 'rm -f "$TMP_JSON" "$TMP_YAML"' EXIT
echo "[INFO] 設定取得を開始します..."
# 1. JSONソースの取得 (GitHub APIを例に)
# -s: 進捗非表示, -L: リダイレクト追従, --retry: 失敗時のリトライ回数
SOURCE_URL="https://api.example.com/v1/config"
if ! curl -sL --retry 3 --connect-timeout 5 "$SOURCE_URL" -o "$TMP_JSON"; then
echo "[ERROR] ソースの取得に失敗しました。" >&2
exit 1
fi
# 2. jqによる検証
# -e: 結果がnullまたはfalseの場合に終了ステータスを1にする
if ! jq -e . "$TMP_JSON" > /dev/null; then
echo "[ERROR] 無効なJSONフォーマットです。" >&2
exit 1
fi
# 3. yqによるJSONからYAMLへの変換と加工
# eval: 式を実行, -P: 読みやすく整形(Pretty print)
echo "[INFO] JSONをYAMLに変換中..."
yq eval -P "$TMP_JSON" > "$TMP_YAML"
# 4. 特定の環境変数をYAMLに注入 (例: DBホストの書き換え)
# env(DB_HOST) でシェル変数を参照
export DB_HOST=${DB_HOST:-"localhost"}
yq eval -i '.database.host = env(DB_HOST)' "$TMP_YAML"
# 5. アトミックなファイル更新 (上書きエラー防止)
# installコマンドは権限設定も同時に行えるため推奨
echo "[INFO] 設定ファイルを反映します: $DEST_CONFIG"
sudo install -m 644 -o root -g root "$TMP_YAML" "$DEST_CONFIG"
echo "[SUCCESS] 自動生成が完了しました。"
systemdによる定期実行の自動化
このスクリプトを30分ごとに実行するためのユニット定義例です。
/etc/systemd/system/config-sync.service
[Unit] Description=Sync Remote Config to Local YAML After=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/sync-config.sh User=root # 必要に応じて環境変数をロード EnvironmentFile=/etc/default/myapp-sync
/etc/systemd/system/config-sync.timer
[Unit] Description=Run config-sync every 30 minutes [Timer] OnBootSec=5min OnUnitActiveSec=30min [Install] WantedBy=timers.target
【検証と運用】
正常系の確認
設定ファイルが意図した構造になっているか、yq を使って再度確認します。
# 生成されたYAMLの内容をデバッグ表示 yq eval '.' /etc/myapp/config.yaml # systemdタイマーの稼働状況確認 systemctl list-timers config-sync.timer
異常系の確認(ログ確認)
スクリプトが失敗した場合、journalctl でエラー箇所を特定します。
journalctl -u config-sync.service -n 50 --no-pager
【トラブルシューティングと落とし穴】
権限問題 (Sudo):
installコマンドで/etc配下に書き込む際、非ルートユーザーで実行していると失敗します。systemdユニットでUser=rootを指定するか、書き込み先ディレクトリの権限を適切に調整してください。
一時ファイルの肥大化:
trap処理が漏れると/tmpにゴミが残ります。スクリプト冒頭でのtrap '...' EXITは必須です。
yq のバージョン差異:
- Python製の
yq(kislyuk/yq) と Go製のyq(mikefarah/yq) ではコマンド引数が全く異なります。本稿では主流の Go版(v4以降)を前提としています。
- Python製の
【まとめ】
運用の冪等性を維持するための3つのポイント:
検証の強制: 変換前に
jqで構造チェックを行い、不正なデータで既存設定を上書きさせない。アトミック更新:
cpではなくinstallやmvを用いて、ファイル書き込み中の不完全な状態をサービスに読み込ませない。状態の外部化: 環境依存のパラメータはスクリプト内にハードコードせず、
yqのenv()機能を活用して環境変数から注入する。

